@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,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
|
+
```
|