@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.
Files changed (77) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +9 -8
  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 +10 -10
  7. package/vendor/l5x/SKILL.md +27 -7
  8. package/vendor/l5x/lib/engine/change-set.d.ts +9 -1
  9. package/vendor/l5x/lib/engine/change-set.js +32 -7
  10. package/vendor/l5x/lib/engine/documentation.d.ts +58 -0
  11. package/vendor/l5x/lib/engine/documentation.js +237 -0
  12. package/vendor/l5x/lib/engine/instructions.d.ts +50 -0
  13. package/vendor/l5x/lib/engine/instructions.js +107 -0
  14. package/vendor/l5x/lib/engine/ladder.d.ts +74 -0
  15. package/vendor/l5x/lib/engine/ladder.js +209 -0
  16. package/vendor/l5x/lib/index.d.ts +1 -0
  17. package/vendor/l5x/lib/index.js +3 -1
  18. package/vendor/l5x/lib/model/routines.js +6 -1
  19. package/vendor/l5x/lib/session.d.ts +20 -0
  20. package/vendor/l5x/lib/session.js +36 -0
  21. package/vendor/l5x/lib/tools/document.d.ts +18 -0
  22. package/vendor/l5x/lib/tools/document.js +119 -0
  23. package/vendor/l5x/lib/tools/drafts.js +109 -3
  24. package/vendor/optix/SKILL.md +68 -0
  25. package/vendor/optix/netlogic/SOURCE.json +12 -0
  26. package/vendor/optix/netlogic/index.json +1987 -0
  27. package/vendor/optix/netlogic/pages/accessing-project-nodes.md +113 -0
  28. package/vendor/optix/netlogic/pages/advanced-svg.md +124 -0
  29. package/vendor/optix/netlogic/pages/alarming.md +489 -0
  30. package/vendor/optix/netlogic/pages/async-tasks.md +236 -0
  31. package/vendor/optix/netlogic/pages/audit-signature.md +91 -0
  32. package/vendor/optix/netlogic/pages/colors.md +122 -0
  33. package/vendor/optix/netlogic/pages/companion-specs.md +77 -0
  34. package/vendor/optix/netlogic/pages/creating-objects.md +370 -0
  35. package/vendor/optix/netlogic/pages/database-interaction.md +437 -0
  36. package/vendor/optix/netlogic/pages/datagrids.md +123 -0
  37. package/vendor/optix/netlogic/pages/debugging-tips.md +605 -0
  38. package/vendor/optix/netlogic/pages/designtime-tags-import.md +531 -0
  39. package/vendor/optix/netlogic/pages/dialog-boxes.md +150 -0
  40. package/vendor/optix/netlogic/pages/dispatch-events.md +200 -0
  41. package/vendor/optix/netlogic/pages/dynamic-links.md +825 -0
  42. package/vendor/optix/netlogic/pages/element-access.md +1208 -0
  43. package/vendor/optix/netlogic/pages/engineering-units.md +298 -0
  44. package/vendor/optix/netlogic/pages/events.md +340 -0
  45. package/vendor/optix/netlogic/pages/execute-command.md +202 -0
  46. package/vendor/optix/netlogic/pages/fto-studio-cli.md +99 -0
  47. package/vendor/optix/netlogic/pages/good-practices.md +50 -0
  48. package/vendor/optix/netlogic/pages/information-model.md +89 -0
  49. package/vendor/optix/netlogic/pages/log-output.md +22 -0
  50. package/vendor/optix/netlogic/pages/managing-aliases.md +22 -0
  51. package/vendor/optix/netlogic/pages/manipulate-nodes.md +72 -0
  52. package/vendor/optix/netlogic/pages/mqtt.md +240 -0
  53. package/vendor/optix/netlogic/pages/netlogic-lifecycle.md +270 -0
  54. package/vendor/optix/netlogic/pages/netlogic-overview.md +155 -0
  55. package/vendor/optix/netlogic/pages/opcua.md +325 -0
  56. package/vendor/optix/netlogic/pages/plc-tags.md +219 -0
  57. package/vendor/optix/netlogic/pages/queries.md +1297 -0
  58. package/vendor/optix/netlogic/pages/random-generation.md +90 -0
  59. package/vendor/optix/netlogic/pages/recipes.md +375 -0
  60. package/vendor/optix/netlogic/pages/recipex.md +2383 -0
  61. package/vendor/optix/netlogic/pages/regex.md +15 -0
  62. package/vendor/optix/netlogic/pages/register-observers.md +346 -0
  63. package/vendor/optix/netlogic/pages/reports.md +141 -0
  64. package/vendor/optix/netlogic/pages/resource-uri.md +308 -0
  65. package/vendor/optix/netlogic/pages/runtime-tags-import.md +280 -0
  66. package/vendor/optix/netlogic/pages/serial-port.md +357 -0
  67. package/vendor/optix/netlogic/pages/sessions.md +58 -0
  68. package/vendor/optix/netlogic/pages/system-node.md +153 -0
  69. package/vendor/optix/netlogic/pages/template-library.md +127 -0
  70. package/vendor/optix/netlogic/pages/threading.md +236 -0
  71. package/vendor/optix/netlogic/pages/translations.md +417 -0
  72. package/vendor/optix/netlogic/pages/trends.md +128 -0
  73. package/vendor/optix/netlogic/pages/ui-animations.md +115 -0
  74. package/vendor/optix/netlogic/pages/users-groups.md +416 -0
  75. package/vendor/optix/netlogic/pages/variables-formatting.md +16 -0
  76. package/vendor/optix/netlogic/pages/variables-interaction.md +557 -0
  77. package/vendor/optix/netlogic/pages/variables-structure.md +155 -0
@@ -0,0 +1,357 @@
1
+ # Serial port
2
+
3
+ Communication using the serial port is only allowed using the SerialPort driver provided by FactoryTalk Optix.
4
+
5
+ On Windows, the name of the COM port must be specified as `COMx`, where `x` is the port number (e.g., `COM1`, `COM2`, etc.). On embedded devices, the name of the serial port is automatically mapped to the serial port index of the device (e.g., `COM0` is mapped to the serial port at index 0 - the embedded serial port of OptixPanels - which is internally mapped to `/dev/serial0`).
6
+
7
+ > [!TIP]
8
+ > For the OptixPanel standard, the embedded serial port is available as `COM1` (internally mapped to `/dev/serial1`).
9
+
10
+ So, if the project configures `COM0` as serial port name, on Windows it will try to open `COM0`, while on embedded devices it will try to open `/dev/serial0` with no need to change anything on the project.
11
+
12
+ > [!NOTE]
13
+ > All the serial port write operations (like the `Write` and `WriteBytes` methods) are **non-blocking**, meaning that the NetLogic code will continue immediately even if the data is not yet written to the serial port.
14
+
15
+ > [!NOTE]
16
+ > Serial port communication can only be performed at runtime. Attempting to read from or write to the serial port at design time will not work.
17
+
18
+ ## Polling mode
19
+
20
+ ```csharp
21
+ public class RuntimeNetlogic1 : BaseNetLogic
22
+ {
23
+ private SerialPort serialPort;
24
+ private PeriodicTask periodicTask;
25
+
26
+ public override void Start()
27
+ {
28
+ serialPort = (SerialPort)Owner;
29
+ periodicTask = new PeriodicTask(Read, 300, Owner);
30
+ periodicTask.Start();
31
+ }
32
+
33
+ private void Read()
34
+ {
35
+ try
36
+ {
37
+ ReadImpl();
38
+ }
39
+ catch (Exception ex)
40
+ {
41
+ Log.Error("Failed to read from Modbus: " + ex);
42
+ }
43
+ }
44
+
45
+ private void ReadImpl()
46
+ {
47
+ serialPort.WriteBytes(Serialize());
48
+ var result = serialPort.ReadBytes(3);
49
+ if ((result[1] & 0x80) == 0)
50
+ {
51
+ result = serialPort.ReadBytes((uint)(result[2] + 2));
52
+ Log.Info(Deserialize(result));
53
+ }
54
+ else
55
+ {
56
+ Log.Error("Failed to read from Modbus");
57
+ }
58
+ }
59
+
60
+ private byte[] Serialize()
61
+ {
62
+ var buffer = new byte[]
63
+ {
64
+ 0x01, // UnitId
65
+ 0x03, // Function code
66
+ 0x00, // Starting address
67
+ 0x00,
68
+ 0x00, // Quantity Of Registers
69
+ 0x01,
70
+ 0x84, // CRC
71
+ 0x0a
72
+ };
73
+ return buffer;
74
+ }
75
+
76
+ private ushort Deserialize(byte[] buffer)
77
+ {
78
+ var first = (ushort)buffer[1];
79
+ var second = (ushort)(buffer[0] << 8);
80
+ return (ushort)(first | second);
81
+ }
82
+ }
83
+ ```
84
+
85
+ ## Event mode
86
+
87
+ ```csharp
88
+ public class RuntimeNetlogic1 : BaseNetLogic
89
+ {
90
+ private SerialPort serialPort;
91
+ private LongRunningTask task;
92
+
93
+ public override void Start()
94
+ {
95
+ serialPort = (SerialPort)Owner;
96
+ serialPort.Timeout = TimeSpan.FromMilliseconds(0.0);
97
+ task = new LongRunningTask(Run, Owner);
98
+ task.Start();
99
+ }
100
+
101
+ [ExportMethod]
102
+ public void OnClick()
103
+ {
104
+ // Cancel current read
105
+ serialPort.CancelRead();
106
+ task.Cancel();
107
+ // Start new read
108
+ task = new LongRunningTask(Run, Owner);
109
+ task.Start();
110
+ }
111
+
112
+ private void Run()
113
+ {
114
+ while(!task.IsCancellationRequested)
115
+ {
116
+ try
117
+ {
118
+ // Block until 3 bytes arrive on serial
119
+ var result = serialPort.ReadBytes(3);
120
+ foreach (var b in result)
121
+ Log.Info(String.Format("0x{0:x2}", b));
122
+ }
123
+ catch (ReadCanceledException ex)
124
+ {
125
+ // In case of read canceled, exit from the loop
126
+ Log.Info(ex.Message);
127
+ return;
128
+ }
129
+ catch (Exception e)
130
+ {
131
+ Log.Error(e.Message);
132
+ }
133
+ }
134
+ }
135
+
136
+ public override void Stop()
137
+ {
138
+ // Explicit call to Close to cancel pending read (if any)
139
+ serialPort.Close();
140
+ task.Cancel();
141
+ }
142
+ }
143
+ ```
144
+
145
+ ## Custom protocol implementation example
146
+
147
+ ```csharp
148
+ #region Using directives
149
+ using System;
150
+ using UAManagedCore;
151
+ using OpcUa = UAManagedCore.OpcUa;
152
+ using FTOptix.UI;
153
+ using FTOptix.NativeUI;
154
+ using FTOptix.HMIProject;
155
+ using FTOptix.Retentivity;
156
+ using FTOptix.CoreBase;
157
+ using FTOptix.Core;
158
+ using FTOptix.NetLogic;
159
+ using FTOptix.SerialPort;
160
+ using FTOptix.CommunicationDriver;
161
+ using System.Collections.Generic;
162
+ #endregion
163
+
164
+ public class SerialPortLogic : BaseNetLogic
165
+ {
166
+ public override void Start()
167
+ {
168
+ // Get the input and output variables
169
+ IncomingString = LogicObject.GetVariable("IncomingString");
170
+ OutgoingString = LogicObject.GetVariable("OutgoingString");
171
+ // Prepare the event to be triggered when the OutgoingString variable changes
172
+ OutgoingString.VariableChange += OutgoingString_VariableChange;
173
+ // Starts the COM port reading task as soon as the user-defined logic is started
174
+ comPort = (SerialPort)Owner; // Cast the Owner property to the SerialPort type
175
+ comPort.Timeout = TimeSpan.FromMilliseconds(100); // Adjust as needed
176
+ serialPortTask = new LongRunningTask(ReadSerialPort, LogicObject); // Create a new task to read the COM port
177
+ serialPortTask.Start(); // Start the task
178
+ }
179
+
180
+ private void OutgoingString_VariableChange(object sender, VariableChangeEventArgs e)
181
+ {
182
+ dataToSend = e.NewValue.Value.ToString();
183
+ }
184
+
185
+ public override void Stop()
186
+ {
187
+ OutgoingString.VariableChange -= OutgoingString_VariableChange;
188
+ serialPortTask?.Dispose();
189
+ }
190
+
191
+ private void ReadSerialPort()
192
+ {
193
+ Log.Info("ReadSerialPort", "Starting to read the COM port");
194
+ byte incomingChar;
195
+ List<byte> buffer = [];
196
+
197
+ // This loop keeps reading the COM port in case of an exception
198
+ while (!serialPortTask.IsCancellationRequested)
199
+ {
200
+ Log.Info("ReadSerialPort", "Entering main loop");
201
+
202
+ try
203
+ {
204
+ Log.Info("ReadSerialPort", "Opening the COM port");
205
+ // Open the COM port
206
+ comPort.Open();
207
+ incomingChar = 0x00;
208
+ Log.Info("ReadSerialPort", "COM port opened");
209
+
210
+ // This loop reads the COM port until the termination character is received
211
+ while (!serialPortTask.IsCancellationRequested)
212
+ {
213
+ try
214
+ {
215
+ // Read the incoming character
216
+ incomingChar = comPort.ReadByte();
217
+
218
+ // If the incoming character is not the termination character, add it to the buffer
219
+ if (incomingChar != terminationChar)
220
+ {
221
+ buffer.Add(incomingChar);
222
+ }
223
+ else
224
+ {
225
+ // If the incoming character is the termination character,
226
+ // convert the buffer to a string and set the IncomingString variable
227
+ string message = System.Text.Encoding.ASCII.GetString(buffer.ToArray());
228
+ IncomingString.Value = message;
229
+ Log.Info("ReadSerialPort", $"Received message: {message}");
230
+ buffer.Clear();
231
+ }
232
+ }
233
+ catch (Exception ex)
234
+ {
235
+ if (!ex.Message.Contains("read timeout"))
236
+ {
237
+ // If another exception occurs, log the error and break the loop
238
+ Log.Error("ReadSerialPort", ex.Message);
239
+ throw;
240
+ }
241
+ }
242
+
243
+ // Check if data needs to be sent
244
+ if (dataToSend != null)
245
+ {
246
+ // Send the data and the termination character
247
+ Log.Info("ReadSerialPort", "Sending data: " + dataToSend);
248
+ comPort.WriteBytes(System.Text.Encoding.ASCII.GetBytes(dataToSend));
249
+ comPort.WriteBytes(new byte[] { terminationChar });
250
+ dataToSend = null;
251
+ }
252
+ }
253
+ }
254
+ catch (Exception ex)
255
+ {
256
+ // Insert code to handle exceptions
257
+ Log.Error("ReadSerialPort", ex.Message);
258
+ comPort.Close();
259
+ }
260
+ finally
261
+ {
262
+ Log.Info("ReadSerialPort", "Closing the COM port");
263
+ comPort.Close();
264
+ }
265
+ }
266
+
267
+ Log.Info("ReadSerialPort", "Exiting main loop");
268
+ }
269
+
270
+ private const byte terminationChar = 0x0A;
271
+ private IUAVariable IncomingString;
272
+ private IUAVariable OutgoingString;
273
+ private SerialPort comPort;
274
+ private LongRunningTask serialPortTask;
275
+ private string dataToSend;
276
+ }
277
+ ```
278
+
279
+ ## Using the serial port to control an external device
280
+
281
+ This example shows how to keep the TX line HIGH on a serial port to power an external device that requires it (like to power an external relay module).
282
+
283
+ > [!WARNING]
284
+ > The TX line of a serial port is not designed to provide power to external devices. Ensure that the external device's power requirements do not exceed the capabilities of the serial port to avoid damage. An intermediate driver circuit (current or voltage buffer) is strictly required.
285
+
286
+ > [!NOTE]
287
+ > Depending on the hardware interface, the TX line might be inverted. Please check the hardware documentation for more details.
288
+
289
+ ```csharp
290
+ using System;
291
+ using System.Linq;
292
+ using FTOptix.CommunicationDriver;
293
+ using FTOptix.NetLogic;
294
+ using FTOptix.SerialPort;
295
+ using UAManagedCore;
296
+
297
+ public class MinimalSerialPortLogic : BaseNetLogic
298
+ {
299
+ public override void Start()
300
+ {
301
+ // Setup Serial Port
302
+ serialPort = (SerialPort)Owner;
303
+ serialPort.Baudrate = 9600;
304
+ serialPort.FlowControl = FlowControl.None;
305
+ serialPort.DataSize = 8;
306
+ serialPort.Parity = Parity.None;
307
+ serialPort.StopBits = StopBits.One;
308
+ serialPort.Timeout = TimeSpan.FromMilliseconds(100);
309
+
310
+ // Calculate buffer size for 2 seconds transmission
311
+ // Baudrate: 9600 bits/sec
312
+ // Bits per byte: 10 (1 start + 8 data + 1 stop)
313
+ // Bytes per second: 9600 / 10 = 960 bytes/sec
314
+ // For 2000ms: 960 * 2 = 1920 bytes
315
+ var bytesPerSecond = serialPort.Baudrate / (serialPort.DataSize + 2);
316
+ var bufferSize = (bytesPerSecond * TX_DURATION_MS) / 1000;
317
+ txBuffer = Enumerable.Repeat((byte)0x00, (int)bufferSize).ToArray();
318
+
319
+ Log.Info("MinimalSerialPortLogic", $"Initialized with buffer size: {txBuffer.Length} bytes for {TX_DURATION_MS}ms transmission");
320
+ }
321
+
322
+ public override void Stop()
323
+ {
324
+ if (serialPort != null)
325
+ {
326
+ serialPort.Close();
327
+ serialPort = null;
328
+ }
329
+ }
330
+
331
+ /// <summary>
332
+ /// Activates the TX pin for the configured duration by writing the pre-calculated buffer.
333
+ /// WriteBytes is asynchronous - it queues the data and returns immediately.
334
+ /// The actual transmission will take approximately TX_DURATION_MS.
335
+ /// </summary>
336
+ [ExportMethod]
337
+ public void SetOutput()
338
+ {
339
+ try
340
+ {
341
+ var stopwatch = System.Diagnostics.Stopwatch.StartNew();
342
+ serialPort.WriteBytes(txBuffer);
343
+ stopwatch.Stop();
344
+
345
+ Log.Info("SetOutput", $"TX activated. Buffer: {txBuffer.Length} bytes, Queue time: {stopwatch.ElapsedMilliseconds}ms, Expected TX duration: {TX_DURATION_MS}ms");
346
+ }
347
+ catch (Exception ex)
348
+ {
349
+ Log.Error("SetOutput", $"Failed to write to serial port: {ex.Message}");
350
+ }
351
+ }
352
+
353
+ private SerialPort serialPort;
354
+ private byte[] txBuffer;
355
+ private const int TX_DURATION_MS = 2000;
356
+ }
357
+ ```
@@ -0,0 +1,58 @@
1
+ # Sessions
2
+
3
+ > [!NOTE]
4
+ > Session operations can only be performed at runtime. Attempting to manipulate sessions at design time will not work.
5
+
6
+ ## Getting the number of the WebPresentationEngine sessions
7
+
8
+ ```csharp
9
+ /// <summary>
10
+ /// Retrieves the number of active Web sessions
11
+ /// </summary>
12
+ /// <param name="webPresentationEngineNodeId">NodeId of the WebPresentationEngine node</param>
13
+ /// <param name="activeWebSessionNumber">Counting active web sessions</param>
14
+ [ExportMethod]
15
+ public void GetActiveWebSessionsNumber(NodeId webPresentationEngineNodeId, out int activeWebSessionNumber)
16
+ {
17
+ try
18
+ {
19
+ // Get the current WebPresentationEngine from the project
20
+ PresentationEngine webPresentationEngine = InformationModel.Get<PresentationEngine>(webPresentationEngineNodeId);
21
+ // Call the method to read the active sessions
22
+ activeWebSessionNumber = GetWebPresentationEngineSessions(webPresentationEngine).Count();
23
+ }
24
+ catch (Exception ex)
25
+ {
26
+ Log.Error(ex.Message);
27
+ activeWebSessionNumber = -1;
28
+ }
29
+ }
30
+
31
+ // Use reflections to get the sessions (check where user is present (like Anonymous))
32
+ private static IEnumerable<UISession> GetWebPresentationEngineSessions(PresentationEngine webPresentationEngine) => webPresentationEngine.Sessions.Where(s => s.User != null);
33
+ ```
34
+
35
+ ## Impersonate a session
36
+
37
+ Every operation being done on the Optix core must be done in the context of a session. When you are invoking code from an asynchronous method, you must always bind to an existing session, even if for a short time.
38
+
39
+ > [!TIP]
40
+ > This is a very edge case, if you need to perform asynchronous operation without having to deal with sessions, it is a better choice to use [async tasks](./async-tasks.md) instead.
41
+
42
+ If no session is impersonated, the code will likely cause the runtime to crash (even with simple operation like toggling a variable).
43
+
44
+ ```csharp
45
+ public void DoSomethingAsynchronously()
46
+ {
47
+ // Get the first available session
48
+ var sessionHandler = LogicObject.Context.Sessions.ImpersonateRootTemporary();
49
+ if (sessionHandler != null)
50
+ {
51
+ // Do something in the context of the session
52
+ var someVariable = InformationModel.GetVariable("SomeVariable");
53
+ someVariable.Value = true;
54
+ }
55
+ // Always dispose the session handler to release the session
56
+ sessionHandler?.Dispose();
57
+ }
58
+ ```
@@ -0,0 +1,153 @@
1
+ # The system object
2
+
3
+ The "System" object is a special node which provides some methods and events to interact with Optix devices (like OptixPanel, OptixEdge, etc)
4
+
5
+ > [!NOTE]
6
+ > The System node can only be manipulated at runtime. Attempting to call its methods or subscribe to its events at design time will not work.
7
+
8
+ ## Load a new application
9
+
10
+ Please note:
11
+ - Application must be exported by FactoryTalk Optix Studio with the same version that was previously used to deploy the original application
12
+ - Exported application must be protected by a password
13
+ - The import procedure might take some time, please wait for the confirmation message
14
+
15
+ ```csharp
16
+ #region Using directives
17
+ using System;
18
+ using UAManagedCore;
19
+ using OpcUa = UAManagedCore.OpcUa;
20
+ using FTOptix.UI;
21
+ using FTOptix.NativeUI;
22
+ using FTOptix.HMIProject;
23
+ using FTOptix.Retentivity;
24
+ using FTOptix.CoreBase;
25
+ using FTOptix.Core;
26
+ using FTOptix.NetLogic;
27
+ using FTOptix.System;
28
+ using System.IO;
29
+ using System.Runtime.Versioning;
30
+ #endregion
31
+
32
+ public class RuntimeNetLogic1 : BaseNetLogic
33
+ {
34
+ public override void Start()
35
+ {
36
+ // Nothing to do
37
+ }
38
+
39
+ public override void Stop()
40
+ {
41
+ systemNode.OnLoadApplicationStatusEvent -= SystemNode_OnLoadApplicationStatusEvent;
42
+ }
43
+
44
+ [ExportMethod]
45
+ public void LoadNewApplication(string password, bool deleteApplicationFiles)
46
+ {
47
+ // Get to the system node
48
+ systemNode = Project.Current.Get<FTOptix.System.System>("System/System");
49
+ // Get the new application path
50
+ var newApplicationPath = ResourceUri.FromProjectRelativePath("NewApplication.z");
51
+
52
+ // Subscribe to the application load event
53
+ systemNode.OnLoadApplicationStatusEvent += SystemNode_OnLoadApplicationStatusEvent;
54
+
55
+ // Load the new application
56
+ if (systemNode.LoadApplication(newApplicationPath.Uri, password, deleteApplicationFiles))
57
+ {
58
+ // Call to the SystemManager API to load the new application was successful
59
+ Log.Info("LoadApplication", "Load application call was successful, please wait for the process to complete");
60
+ }
61
+ else
62
+ {
63
+ // Call to the SystemManager API to load the new application failed
64
+ Log.Error("LoadApplication", "Failed to load application, call to the SystemManager APIs failed");
65
+ }
66
+ }
67
+
68
+ private void SystemNode_OnLoadApplicationStatusEvent(object sender, LoadApplicationStatusEvent e)
69
+ {
70
+ if (e.Status == LoadApplicationStatus.Success)
71
+ {
72
+ // Application loaded successfully
73
+ Log.Info("LoadApplicationStatus", "Application loaded successfully");
74
+ }
75
+ else
76
+ {
77
+ // Application failed to load
78
+ Log.Error("LoadApplicationStatus", "Failed to load application: " + e.Status);
79
+ }
80
+ }
81
+
82
+ FTOptix.System.System systemNode;
83
+ }
84
+ ```
85
+
86
+ ## Example: system and session variables (user, time, USB, IP)
87
+
88
+ ```csharp
89
+ /// <summary>
90
+ /// Periodically populate NetLogic variables with system and session information
91
+ /// (active user, locale, current time components, random values and network/IP).
92
+ /// Intended for Runtime NetLogic.
93
+ /// </summary>
94
+ private Random rnd = new Random();
95
+
96
+ private void OneSecondSystemVariables()
97
+ {
98
+ // User and session info
99
+ LogicObject.GetVariable("ActiveUser").Value = Session.User.BrowseName;
100
+ LogicObject.GetVariable("ActiveUserLocaleId").Value = Session.User.LocaleIds[0];
101
+ try { LogicObject.GetVariable("ActiveUserLanguage").Value = Session.User.Languages[0]; }
102
+ catch { LogicObject.GetVariable("ActiveUserLanguage").Value = "Language not selected"; }
103
+
104
+ // Time information
105
+ LogicObject.GetVariable("ActTimeSec").Value = DateTime.Now.Second;
106
+ LogicObject.GetVariable("ActTimeMin").Value = DateTime.Now.Minute;
107
+ LogicObject.GetVariable("ActTimeHour").Value = DateTime.Now.Hour;
108
+ LogicObject.GetVariable("ActTimeDay").Value = DateTime.Now.Day;
109
+ LogicObject.GetVariable("ActTimeMonth").Value = DateTime.Now.Month;
110
+ LogicObject.GetVariable("ActTimeYear").Value = DateTime.Now.Year;
111
+ LogicObject.GetVariable("ActTimeDayOfWeek").Value = (int)DateTime.Now.DayOfWeek;
112
+ LogicObject.GetVariable("ActTimeString").Value = DateTime.Now.ToShortTimeString();
113
+ LogicObject.GetVariable("ActDateString").Value = DateTime.Now.ToShortDateString();
114
+
115
+ // Random numbers
116
+ LogicObject.GetVariable("SimRandInt").Value = rnd.Next(32768);
117
+ LogicObject.GetVariable("SimSinDouble").Value = Math.Sin(Math.PI * rnd.Next(361) / 180.0);
118
+ LogicObject.GetVariable("SimCosDouble").Value = Math.Cos(Math.PI * rnd.Next(361) / 180.0);
119
+
120
+ // Active alarms count (safe navigation)
121
+ IContext context = LogicObject.Context;
122
+ var retainedAlarms = context.GetNode(QPlatform.Alarm.Objects.RetainedAlarms);
123
+ var localizedAlarmsVariable = retainedAlarms?.Children.Get<IUAVariable>("LocalizedAlarms");
124
+ var localizedAlarmsNodeId = localizedAlarmsVariable != null ? (NodeId)localizedAlarmsVariable.Value : NodeId.Empty;
125
+ IUANode localizedAlarmsContainer = null;
126
+ if (localizedAlarmsNodeId != null && !localizedAlarmsNodeId.IsEmpty)
127
+ localizedAlarmsContainer = context.GetNode(localizedAlarmsNodeId);
128
+ LogicObject.Children.Get<IUAVariable>("NumActiveAlarms").Value = localizedAlarmsContainer?.Children.Count ?? 0;
129
+ LogicObject.Children.Get<IUAVariable>("LastAlarmText").Value = localizedAlarmsContainer?.Children.LastOrDefault()?.BrowseName ?? "";
130
+
131
+ // USB present detection using ResourceUri catch
132
+ var pathUsbResourceUri = new ResourceUri("%USB1%");
133
+ try { var uri = pathUsbResourceUri.Uri; LogicObject.GetVariable("UsbPresent").Value = true; }
134
+ catch { LogicObject.GetVariable("UsbPresent").Value = false; }
135
+
136
+ // Network parameters (Windows only)
137
+ if (LogicObject.GetVariable("Windows").Value)
138
+ {
139
+ try
140
+ {
141
+ string hostName = Dns.GetHostName();
142
+ var host = Dns.GetHostEntry(hostName);
143
+ var ipv4s = host.AddressList.Where(a => a.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork).ToArray();
144
+ if (ipv4s.Length > 0) LogicObject.GetVariable("IP1").Value = ipv4s[0].ToString();
145
+ if (ipv4s.Length > 1) LogicObject.GetVariable("IP2").Value = ipv4s[1].ToString();
146
+ }
147
+ catch (Exception ex)
148
+ {
149
+ Log.Warning("OneSecondSystemVariables", "Unable to read IP addresses: " + ex.Message);
150
+ }
151
+ }
152
+ }
153
+ ```
@@ -0,0 +1,127 @@
1
+ # Template Library
2
+
3
+ ## Import an object from the template library
4
+
5
+ This feature allows you to import objects from the template library into your project. The imported object will be placed in the specified folder. The object can be an instance or a type.
6
+
7
+ > [!TIP]
8
+ > This feature got introduced in FactoryTalk Optix 1.6.X
9
+
10
+ > [!NOTE]
11
+ > Template library operations can only be performed at design time. Attempting to manipulate the template library at runtime will not work.
12
+
13
+ ### Import item syntax
14
+
15
+ ```csharp
16
+ public static class TemplateLibrary
17
+ {
18
+ public static IUANode ImportLibraryItem(IUANode destinationNode, NodeClass nodeClass, string libraryName,
19
+ string itemPath, List<TypeConflictResolutionChoice> conflictResolutionChoices = null,
20
+ bool preserveTypeDependencyPaths = false);
21
+ }
22
+ ```
23
+
24
+ ### Import the LoginForm from the Widgets library example
25
+
26
+ ```csharp
27
+ [ExportMethod]
28
+ public void ImportLoginFormFromLibrary()
29
+ {
30
+ // The place where the object will be imported
31
+ var targetFolder = Project.Current.Get("UI");
32
+ // Import the object
33
+ // Syntax: ImportLibraryItem(<item destination>, <node class (type or instance)>, <name of the library>, <name of the library item>)
34
+ var myWidget = TemplateLibrary.ImportLibraryItem(targetFolder, NodeClass.ObjectType, "Widgets", "LoginForm");
35
+ // Log the result
36
+ Log.Info($"Imported LoginForm from library to: {Log.Node(myWidget)}");
37
+ }
38
+ ```
39
+
40
+ ## Specifying a conflict resolution
41
+
42
+ If the imported element already exists in the target folder, you can specify a conflict resolution strategy.
43
+
44
+ ### Conflict resolution syntax
45
+
46
+ A list of `TypeConflictResolutionChoice` structures is used to specify the conflict resolution strategy. The structure contains the following fields:
47
+
48
+ ```csharp
49
+ public struct TypeConflictResolutionChoice
50
+ {
51
+ public TypeConflictResolutionChoice(string browseName, NodeClass nodeClass, ConflictResolution resolution)
52
+ {
53
+ BrowseName = browseName;
54
+ NodeClass = nodeClass;
55
+ Resolution = resolution;
56
+ }
57
+
58
+ public readonly string BrowseName;
59
+ public readonly NodeClass NodeClass;
60
+ public readonly ConflictResolution Resolution;
61
+ }
62
+ ```
63
+
64
+ ### Conflict resolution example
65
+
66
+ The following example will import the LoginForm from the TeamplateLibrary by overwriting the existing element (if any):
67
+
68
+ ```csharp
69
+ using UAManagedCore;
70
+ using FTOptix.HMIProject;
71
+ using FTOptix.NetLogic;
72
+ using System.Collections.Generic;
73
+ using static UAManagedCore.TemplateLibrary;
74
+
75
+ public class DesignTimeNetLogic1 : BaseNetLogic
76
+ {
77
+ [ExportMethod]
78
+ public void ImportLoginFormFromLibrary()
79
+ {
80
+ // Conflict resolution strategy
81
+ var conflictResolutionChoices = new List<TypeConflictResolutionChoice>
82
+ {
83
+ // This is the list of elements which are inside the LoginForm object, we can specify how to handle each of them
84
+ new TypeConflictResolutionChoice("LoginForm", NodeClass.ObjectType, ConflictResolution.Replace),
85
+ new TypeConflictResolutionChoice("Login", NodeClass.ObjectType, ConflictResolution.Replace),
86
+ new TypeConflictResolutionChoice("LoginPasswordExpiredDialog", NodeClass.ObjectType, ConflictResolution.Replace),
87
+ new TypeConflictResolutionChoice("LoginChangePasswordForm", NodeClass.ObjectType, ConflictResolution.Replace),
88
+ new TypeConflictResolutionChoice("Logout", NodeClass.ObjectType, ConflictResolution.Replace),
89
+ new TypeConflictResolutionChoice("OAuth2Login", NodeClass.ObjectType, ConflictResolution.Replace)
90
+ };
91
+ // The place where the object will be imported
92
+ var targetFolder = Project.Current.Get("UI");
93
+ // Import the object
94
+ // Syntax: ImportLibraryItem(<item destination>, <node class (type or instance)>, <name of the library>, <name of the library item>)
95
+ var myWidget = TemplateLibrary.ImportLibraryItem(targetFolder, NodeClass.ObjectType, "Widgets", "LoginForm", conflictResolutionChoices);
96
+ // Log the result
97
+ Log.Info($"Imported LoginForm from library to: {Log.Node(myWidget)}");
98
+ }
99
+ }
100
+ ```
101
+
102
+ ## Getting better performance
103
+
104
+ If you are importing a large object with many dependencies, you can improve the performance by calling the import routine using a LongRunningTask.
105
+
106
+ ### Importing a large object using a LongRunningTask
107
+
108
+ ```csharp
109
+ [ExportMethod]
110
+ public void ImportLargeObjectFromLibrary()
111
+ {
112
+ // Import the object
113
+ var importWidgetTask = new LongRunningTask(ImportLoginForm, LogicObject);
114
+ importWidgetTask.Start();
115
+ }
116
+
117
+ private void ImportLoginForm()
118
+ {
119
+ // The place where the object will be imported
120
+ var targetFolder = Project.Current.Get("UI");
121
+ // Import the object
122
+ // Syntax: ImportLibraryItem(<item destination>, <node class (type or instance)>, <name of the library>, <name of the library item>)
123
+ var myWidget = TemplateLibrary.ImportLibraryItem(targetFolder, NodeClass.ObjectType, "Widgets", "LoginForm");
124
+ // Log the result
125
+ Log.Info($"Imported LoginForm from library to: {Log.Node(myWidget)}");
126
+ }
127
+ ```