@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,236 @@
|
|
|
1
|
+
# Asynchronous tasks
|
|
2
|
+
|
|
3
|
+
Asynchronous tasks are very common when running special or long tasks that may negatively impact the overall operation. Asynchronous tasks were mostly designed to be used at Runtime. It is quite frequent to use `LongRunningTask` to run a long process without freezing the FactoryTalk Optix Studio interface even with a DesignTime NetLogic though.
|
|
4
|
+
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> Avoid modifying the project structure (pages, tags, etc) while asynchronous tasks are running, especially at DesignTime. This can cause unexpected behavior—for example, editing the project while a task is importing/exporting data may result in conflicts or errors.
|
|
7
|
+
|
|
8
|
+
## General concepts about Asynchronous tasks
|
|
9
|
+
|
|
10
|
+
Standard C# language provides multiple ways to run asynchronous operations (such as the `async` modifier for some methods), these can be used in FactoryTalk Optix as long as those methods does not access any of the project structure (pages, tags, etc).
|
|
11
|
+
|
|
12
|
+
- Example:
|
|
13
|
+
- async method to read/write a CSV -> GOOD
|
|
14
|
+
- async method to read a CSV and import it into the TranslationDictionary -> BAD
|
|
15
|
+
|
|
16
|
+
## Using FactoryTalk Optix Asynchronous tasks
|
|
17
|
+
|
|
18
|
+
These asynchronous tasks are InformationModel-safe and can be used to access project nodes without concerns
|
|
19
|
+
|
|
20
|
+
### PeriodicTask
|
|
21
|
+
|
|
22
|
+
This kind of task allows a specific method to be run every `x` milliseconds, for example to blink a LED or to handle a watchdog.
|
|
23
|
+
|
|
24
|
+
Please note: the specified interval, is actually the delay between two consecutive runs of the method, and the operation is impacted by the length of the instruction to process. The next run is delayed until the task is completed, for example:
|
|
25
|
+
|
|
26
|
+
Having a task:
|
|
27
|
+
- PeriodicTask declared every 50mS
|
|
28
|
+
- Method run by the PeriodicTask takes 100mS to process
|
|
29
|
+
|
|
30
|
+
With this timings, the task is actually run every 150mS, as the interval is actually the delay after which the task is run again.
|
|
31
|
+
|
|
32
|
+
```csharp
|
|
33
|
+
public override void Start()
|
|
34
|
+
{
|
|
35
|
+
// Creates a task that runs every second without impacting the UI
|
|
36
|
+
myPeriodicTask = new PeriodicTask(IncrementVariable, 1000, LogicObject);
|
|
37
|
+
myPeriodicTask.Start();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public override void Stop()
|
|
41
|
+
{
|
|
42
|
+
// When the NetLogic is disposed, kill the task too
|
|
43
|
+
myPeriodicTask?.Dispose();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private void IncrementVariable()
|
|
47
|
+
{
|
|
48
|
+
// Action to be run every tick of the periodic task
|
|
49
|
+
…
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private PeriodicTask myPeriodicTask;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### DelayedTask
|
|
56
|
+
|
|
57
|
+
This kind of task will trigger a method after `x` milliseconds it is called, this can be useful for example to hide a toast notification after some time
|
|
58
|
+
|
|
59
|
+
```csharp
|
|
60
|
+
public override void Start()
|
|
61
|
+
{
|
|
62
|
+
// Create an action that is run after 10s the page is loaded
|
|
63
|
+
// this could also be a method called by a button or any trigger
|
|
64
|
+
myDelayedTask = new DelayedTask(ResetLabelText, 10000, LogicObject);
|
|
65
|
+
myDelayedTask.Start();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public override void Stop()
|
|
69
|
+
{
|
|
70
|
+
// Make sure to dispose the task when the NetLogic is stopped
|
|
71
|
+
myDelayedTask?.Dispose();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private void ResetLabelText()
|
|
75
|
+
{
|
|
76
|
+
// Action to be run after the delay
|
|
77
|
+
…
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private DelayedTask myDelayedTask;
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### LongRunningTask
|
|
84
|
+
|
|
85
|
+
This is the most similar approach to the `async` modifier of the standard C# scripting, it allows to create a dedicated thread and run it without affecting the main code operation.
|
|
86
|
+
An `async` modifier can also be used, but an async method **cannot** access any project node (it may cause concurrency issues), in such cases, use a LongRunningTask instead.
|
|
87
|
+
|
|
88
|
+
```csharp
|
|
89
|
+
class public override void Start()
|
|
90
|
+
{
|
|
91
|
+
// Create a dedicated thread to run a long or demanding process
|
|
92
|
+
// Without stopping the FactoryTalk Optix Runtime or Studio
|
|
93
|
+
myLongRunningTask = new LongRunningTask(ProcessCSVFile, LogicObject);
|
|
94
|
+
myLongRunningTask.Start();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public override void Stop()
|
|
98
|
+
{
|
|
99
|
+
// Make sure to dispose the thread once the NetLogic is stopped
|
|
100
|
+
myLongRunningTask?.Dispose();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private void ProcessCsvFile(LongRunningTask task)
|
|
104
|
+
{
|
|
105
|
+
// Process that takes long time to be run
|
|
106
|
+
…
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private LongRunningTask myLongRunningTask;
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### Passing arguments to a LongRunningTask
|
|
113
|
+
|
|
114
|
+
An overload of the LongRunningTask constructor accepts a task and an object, the task can be used to check if a cancellation was requested, while the object can be used to pass arguments to the thread that is being generated
|
|
115
|
+
|
|
116
|
+
```csharp
|
|
117
|
+
[ExportMethod]
|
|
118
|
+
public void StartExam(bool free, string clientID = "", string examCode = "")
|
|
119
|
+
{
|
|
120
|
+
Log.Debug(examCode == "" ? "ExamLogic.StartExam" : "ExamLogic.StartExamCertification", "Starting exam");
|
|
121
|
+
startExamTask?.Dispose();
|
|
122
|
+
var argumentsObject = new StartExamParameters { isFree = free, clientID = clientID, examCode = examCode };
|
|
123
|
+
startExamTask = new LongRunningTask(StartExamMethod, argumentsObject, LogicObject);
|
|
124
|
+
startExamTask.Start();
|
|
125
|
+
Log.Debug(examCode == "" ? "ExamLogic.StartExam" : "ExamLogic.StartExamCertification", "Leaving method");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private void StartExamMethod(LongRunningTask task, object examParameters)
|
|
129
|
+
{
|
|
130
|
+
// Get the parameters
|
|
131
|
+
var argumentsObject = (StartExamParameters)examParameters;
|
|
132
|
+
string clientID = argumentsObject.clientID;
|
|
133
|
+
string examCode = argumentsObject.examCode;
|
|
134
|
+
bool free = argumentsObject.isFree;
|
|
135
|
+
// Do other stuff
|
|
136
|
+
// ...
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private sealed class StartExamParameters
|
|
140
|
+
{
|
|
141
|
+
public bool isFree;
|
|
142
|
+
public string clientID;
|
|
143
|
+
public string examCode;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private LongRunningTask startExamTask = null;
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Some general notes on asynchronous tasks
|
|
150
|
+
|
|
151
|
+
### Cancellation token
|
|
152
|
+
|
|
153
|
+
When a task is called asynchronously (especially in LongRunningTask), then the method itself should check if a cancellation request is pending (see [documentation here](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-cancellation)) and act accordingly, for example:
|
|
154
|
+
|
|
155
|
+
```csharp
|
|
156
|
+
public class LongRunningLogic : BaseNetLogic
|
|
157
|
+
{
|
|
158
|
+
public override void Start()
|
|
159
|
+
{
|
|
160
|
+
// Create the new LongRunningTask and run it
|
|
161
|
+
myTask = new LongRunningTask(MyMethod, LogicObject);
|
|
162
|
+
myTask.Start();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
public override void Stop()
|
|
166
|
+
{
|
|
167
|
+
// Request a cancellation event to the async task
|
|
168
|
+
myTask?.Dispose();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
public void MyMethod()
|
|
172
|
+
{
|
|
173
|
+
for (int i = 0; i < 1000; i++)
|
|
174
|
+
{
|
|
175
|
+
// Check if the somebody requested to dispose this task
|
|
176
|
+
if (myTask.IsCancellationRequested)
|
|
177
|
+
return;
|
|
178
|
+
// Do some work here
|
|
179
|
+
Thread.Sleep(1000);
|
|
180
|
+
Owner.Get<Led>("LED1").Active = !Owner.Get<Led>("LED1").Active;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private LongRunningTask myTask;
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Check if a task is running
|
|
189
|
+
|
|
190
|
+
> [!WARNING]
|
|
191
|
+
> This is a non-public API (accessed using C# reflections) and may be subject to changes, please refer to the official documentation to access the publicly available APIs which are guaranteed to be maintained.
|
|
192
|
+
|
|
193
|
+
```csharp
|
|
194
|
+
[ExportMethod]
|
|
195
|
+
public void DoStuff()
|
|
196
|
+
{
|
|
197
|
+
// Initialize the LongRunningTask
|
|
198
|
+
var myTask = new LongRunningTask(MyMethod, LogicObject);
|
|
199
|
+
|
|
200
|
+
// Do some stuff
|
|
201
|
+
|
|
202
|
+
// Check if the task is still running
|
|
203
|
+
while (IsTaskRunning(task))
|
|
204
|
+
{
|
|
205
|
+
Log.Info("The task is still running...");
|
|
206
|
+
Thread.Sleep(100);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private void MyMethod(LongRunningTask task)
|
|
211
|
+
{
|
|
212
|
+
// Do some work
|
|
213
|
+
throw new NotImplementedException();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private static bool IsTaskRunning(LongRunningTask task)
|
|
217
|
+
{
|
|
218
|
+
// Get the type of the LongRunningTask
|
|
219
|
+
Type taskType = task.GetType();
|
|
220
|
+
|
|
221
|
+
// Get the FieldInfo object for the isRunning field
|
|
222
|
+
FieldInfo isRunningField = taskType.GetField("isRunning", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
223
|
+
|
|
224
|
+
// Check if the field was found
|
|
225
|
+
if (isRunningField != null)
|
|
226
|
+
{
|
|
227
|
+
// Get the value of the isRunning field
|
|
228
|
+
bool isRunning = (bool) isRunningField.GetValue(task);
|
|
229
|
+
return isRunning;
|
|
230
|
+
}
|
|
231
|
+
else
|
|
232
|
+
{
|
|
233
|
+
throw new InvalidOperationException("The isRunning field was not found on the LongRunningTask type.");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Audit Signature (CFR21 workflow)
|
|
2
|
+
|
|
3
|
+
The Audit Signature is a special type of object that can be added to any variable in FactoryTalk Optix, it allows to track the changes on the variable and who made those changes, this is specially useful for CFR21 compliance.
|
|
4
|
+
|
|
5
|
+
The following APIs are intended to be used at DesignTime to prepare the InformationModel with the necessary Audit Signatures.
|
|
6
|
+
|
|
7
|
+
## Add signature to a variable
|
|
8
|
+
|
|
9
|
+
```csharp
|
|
10
|
+
[ExportMethod]
|
|
11
|
+
public void AddSignatureToVariable()
|
|
12
|
+
{
|
|
13
|
+
var sourceVarPointer = LogicObject.GetVariable("SourceVariable") ?? throw new ArgumentException("Cannot find source variable");
|
|
14
|
+
var sourceVar = InformationModel.GetVariable(sourceVarPointer.Value) ?? throw new ArgumentException("Cannot find source variable");
|
|
15
|
+
// Create the audit variable
|
|
16
|
+
var sourceVar = InformationModel.Make<AuditInfo>("AuditSigning Signature");
|
|
17
|
+
sourceVar.Add(auditVar);
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Add signature on all bits of a word
|
|
22
|
+
|
|
23
|
+
```csharp
|
|
24
|
+
[ExportMethod]
|
|
25
|
+
public void CreateAuditOnVariableSingleBits()
|
|
26
|
+
{
|
|
27
|
+
// Check if InputVariable is populated
|
|
28
|
+
var sourceVarPointer = LogicObject.GetVariable("SourceVariable") ?? throw new ArgumentException("Cannot find source variable");
|
|
29
|
+
var sourceVar = InformationModel.GetVariable(sourceVarPointer.Value) ?? throw new ArgumentException("Cannot find source variable");
|
|
30
|
+
|
|
31
|
+
// Check if TargetFolder is populated
|
|
32
|
+
var targetFolderPointer = LogicObject.GetVariable("TargetFolder") ?? throw new ArgumentException("Cannot find target folder");
|
|
33
|
+
var targetFolder = InformationModel.Get<Folder>(targetFolderPointer.Value) ?? throw new ArgumentException("Cannot find target folder");
|
|
34
|
+
|
|
35
|
+
// Prepare object for the method
|
|
36
|
+
var methodArguments = new object[] { sourceVar, targetFolder };
|
|
37
|
+
|
|
38
|
+
// Run the method in a separate thread
|
|
39
|
+
var myTask = new LongRunningTask(CreateSingleBits, methodArguments, LogicObject);
|
|
40
|
+
myTask.Start();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private void CreateSingleBits(LongRunningTask task, object methodArgumentsObj)
|
|
44
|
+
{
|
|
45
|
+
// Extract the method arguments
|
|
46
|
+
var methodArguments = (object[]) methodArgumentsObj;
|
|
47
|
+
var sourceVar = (IUAVariable) methodArguments[0];
|
|
48
|
+
var targetFolder = (Folder) methodArguments[1];
|
|
49
|
+
|
|
50
|
+
// Variable to store the number of bits
|
|
51
|
+
var numberOfBits = 0;
|
|
52
|
+
|
|
53
|
+
// Get the type of the source variable. Add new datatypes if needed
|
|
54
|
+
if (sourceVar.DataType == OpcUa.DataTypes.Int32 || sourceVar.DataType == OpcUa.DataTypes.UInt32)
|
|
55
|
+
numberOfBits = 32;
|
|
56
|
+
else if (sourceVar.DataType == OpcUa.DataTypes.Int16 || sourceVar.DataType == OpcUa.DataTypes.UInt16)
|
|
57
|
+
numberOfBits = 16;
|
|
58
|
+
else if (sourceVar.DataType == OpcUa.DataTypes.Float)
|
|
59
|
+
numberOfBits = 32;
|
|
60
|
+
else if (sourceVar.DataType == OpcUa.DataTypes.Double)
|
|
61
|
+
numberOfBits = 64;
|
|
62
|
+
else
|
|
63
|
+
throw new ArgumentException("Unsupported data type");
|
|
64
|
+
|
|
65
|
+
// Create the variable for each bit and add the auditing
|
|
66
|
+
for (int i = 0; i < numberOfBits; i++)
|
|
67
|
+
{
|
|
68
|
+
// Create the new variable
|
|
69
|
+
var newVariable = InformationModel.MakeVariable($"{sourceVar.BrowseName}_Bit{i}", OpcUa.DataTypes.Boolean);
|
|
70
|
+
newVariable.SetDynamicLink(sourceVar, DynamicLinkMode.ReadWrite);
|
|
71
|
+
|
|
72
|
+
// Modify the dynamic link to read only the bit using HasDynamicLink reference
|
|
73
|
+
var dynamicLinkVariable = newVariable.Refs.GetVariable(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink) as IUAVariable;
|
|
74
|
+
if (dynamicLinkVariable != null)
|
|
75
|
+
{
|
|
76
|
+
dynamicLinkVariable.Value = (string)dynamicLinkVariable.Value + $".{i}";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Create the audit variable
|
|
80
|
+
var auditVar = InformationModel.Make<AuditInfo>("AuditSigning Signature");
|
|
81
|
+
newVariable.Add(auditVar);
|
|
82
|
+
|
|
83
|
+
// Check if the variable already exists and delete it
|
|
84
|
+
if (targetFolder.Children.OfType<IUAVariable>().Any(v => v.BrowseName == newVariable.BrowseName))
|
|
85
|
+
targetFolder.Get(newVariable.BrowseName).Delete();
|
|
86
|
+
|
|
87
|
+
// Add the variable to the target folder
|
|
88
|
+
targetFolder.Add(newVariable);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Colors
|
|
2
|
+
|
|
3
|
+
FactoryTalk Optix allows you to set colors on UI objects (such as `Led`, `Rectangle`, `Panel`, etc.) using the `Color` struct. You can assign colors using pre-defined named colors or create custom colors with specific RGB/ARGB values.
|
|
4
|
+
|
|
5
|
+
## The Color Struct
|
|
6
|
+
|
|
7
|
+
The `Color` struct has the following properties:
|
|
8
|
+
- `A` (byte) - Alpha channel (transparency): 0 = fully transparent, 255 = fully opaque
|
|
9
|
+
- `R` (byte) - Red channel: 0-255
|
|
10
|
+
- `G` (byte) - Green channel: 0-255
|
|
11
|
+
- `B` (byte) - Blue channel: 0-255
|
|
12
|
+
- `ARGB` (uint) - Combined ARGB value as a single 32-bit unsigned integer
|
|
13
|
+
|
|
14
|
+
## Creating Colors
|
|
15
|
+
|
|
16
|
+
### Using Pre-defined Named Colors
|
|
17
|
+
|
|
18
|
+
FT Optix provides hundreds of named colors through the `Colors` static class, such as:
|
|
19
|
+
- `Colors.Red`
|
|
20
|
+
- `Colors.Blue`
|
|
21
|
+
- `Colors.Green`
|
|
22
|
+
- `Colors.Goldenrod`
|
|
23
|
+
- `Colors.Cyan`
|
|
24
|
+
- `Colors.Black`
|
|
25
|
+
- And many more...
|
|
26
|
+
|
|
27
|
+
```csharp
|
|
28
|
+
// Create a LED widget
|
|
29
|
+
var ledWidget = InformationModel.Make<Led>("statusIndicator");
|
|
30
|
+
|
|
31
|
+
// Set the LED color using a pre-defined named color
|
|
32
|
+
ledWidget.Color = Colors.Red;
|
|
33
|
+
|
|
34
|
+
// Add the LED to your UI
|
|
35
|
+
...
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Using ARGB Constructor (Explicit Bytes)
|
|
39
|
+
|
|
40
|
+
Create a custom color by specifying the Alpha, Red, Green, and Blue channels as individual bytes:
|
|
41
|
+
|
|
42
|
+
```csharp
|
|
43
|
+
// Create a LED widget
|
|
44
|
+
var ledWidget = InformationModel.Make<Led>("statusIndicator");
|
|
45
|
+
|
|
46
|
+
// Create a custom color: semi-transparent orange
|
|
47
|
+
// Parameters: (Alpha, Red, Green, Blue)
|
|
48
|
+
// Alpha = 0xff (fully opaque), Red = 0xff, Green = 0xaa, Blue = 0xbb
|
|
49
|
+
var customOrange = new Color(0xff, 0xff, 0xaa, 0xbb);
|
|
50
|
+
|
|
51
|
+
// Assign the custom color to the LED
|
|
52
|
+
ledWidget.Color = customOrange;
|
|
53
|
+
|
|
54
|
+
// Add the LED to your UI
|
|
55
|
+
...
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Using ARGB Constructor (Hex Value)
|
|
59
|
+
|
|
60
|
+
Create a custom color by providing a 32-bit ARGB value as a hex number:
|
|
61
|
+
|
|
62
|
+
```csharp
|
|
63
|
+
// Create a LED widget
|
|
64
|
+
var ledWidget = InformationModel.Make<Led>("statusIndicator");
|
|
65
|
+
|
|
66
|
+
// Create a semi-transparent blue color using a 32-bit ARGB hex value
|
|
67
|
+
// Format: 0xAARRGGBB (AA=Alpha, RR=Red, GG=Green, BB=Blue)
|
|
68
|
+
// 0x80 = 128 (50% transparent), 0x00=Red, 0x00=Green, 0xff=Blue
|
|
69
|
+
var semiTransparentBlue = new Color(0x800000ff);
|
|
70
|
+
|
|
71
|
+
// Assign the custom color to the LED
|
|
72
|
+
ledWidget.Color = semiTransparentBlue;
|
|
73
|
+
|
|
74
|
+
// Add the LED to your UI
|
|
75
|
+
...
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Using System.Drawing.Color
|
|
79
|
+
|
|
80
|
+
You can convert .NET's standard `System.Drawing.Color` to FT Optix `Color` using the `ToArgb()` method:
|
|
81
|
+
|
|
82
|
+
```csharp
|
|
83
|
+
// Create a LED widget
|
|
84
|
+
var ledWidget = InformationModel.Make<Led>("statusIndicator");
|
|
85
|
+
|
|
86
|
+
// Convert a System.Drawing.Color to FT Optix Color
|
|
87
|
+
// Goldenrod is a pre-defined System.Drawing color
|
|
88
|
+
var systemGoldenrod = System.Drawing.Color.Goldenrod;
|
|
89
|
+
var goldenrodColor = new Color(systemGoldenrod.ToArgb());
|
|
90
|
+
|
|
91
|
+
// Assign the color to the LED
|
|
92
|
+
ledWidget.Color = goldenrodColor;
|
|
93
|
+
|
|
94
|
+
// Add the LED to your UI
|
|
95
|
+
...
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Complete Example
|
|
99
|
+
|
|
100
|
+
```csharp
|
|
101
|
+
[ExportMethod]
|
|
102
|
+
public void SetupStatusLights()
|
|
103
|
+
{
|
|
104
|
+
// Create three status indicator LEDs
|
|
105
|
+
var operationalLed = InformationModel.Make<Led>("operationalIndicator");
|
|
106
|
+
var warningLed = InformationModel.Make<Led>("warningIndicator");
|
|
107
|
+
var errorLed = InformationModel.Make<Led>("errorIndicator");
|
|
108
|
+
|
|
109
|
+
// Use pre-defined colors for standard status states
|
|
110
|
+
operationalLed.Color = Colors.Green;
|
|
111
|
+
warningLed.Color = Colors.Yellow;
|
|
112
|
+
errorLed.Color = Colors.Red;
|
|
113
|
+
|
|
114
|
+
// Create a custom semi-transparent overlay color
|
|
115
|
+
var overlayColor = new Color(0x80, 0xff, 0xff, 0xff); // 50% transparent white
|
|
116
|
+
|
|
117
|
+
// Add the LEDs to your UI panel
|
|
118
|
+
Owner.Add(operationalLed);
|
|
119
|
+
Owner.Add(warningLed);
|
|
120
|
+
Owner.Add(errorLed);
|
|
121
|
+
}
|
|
122
|
+
```
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Companion Specifications
|
|
2
|
+
|
|
3
|
+
## Load Companion Specifications
|
|
4
|
+
|
|
5
|
+
- Companion Specifications files can be downloaded from [https://github.com/OPCFoundation/UA-Nodeset](https://github.com/OPCFoundation/UA-Nodeset)
|
|
6
|
+
- Add them to FactoryTalk Optix in one of these paths (make sure to replace your username or the proper FactoryTalk Optix setup folder):
|
|
7
|
+
- `C:\Users\<username>\Documents\Rockwell Automation\FactoryTalk Optix\CompanionSpecifications`
|
|
8
|
+
- `C:\Program Files\Rockwell Automation\FactoryTalk Optix\Studio <version number>\CompanionSpecifications`
|
|
9
|
+
- Restart the IDE after adding files to one of those paths to load new files
|
|
10
|
+
- Check the FactoryTalk Optix output if any error was reported after loading the files
|
|
11
|
+
|
|
12
|
+
> [!NOTE]
|
|
13
|
+
> Companion specifications can only be loaded at design time, so the XML files need to be added before starting the IDE.
|
|
14
|
+
|
|
15
|
+
## Create an instance of a Companion Specs type
|
|
16
|
+
|
|
17
|
+
Please make sure the proper XML file was added to FactoryTalk Optix before starting the script
|
|
18
|
+
|
|
19
|
+
We will load an instance of the BlockType object from the DI specifications, the XML relevant part is:
|
|
20
|
+
|
|
21
|
+
```xml
|
|
22
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
23
|
+
<UANodeSet LastModified="2023-04-13T14:49:52.318Z" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd" xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd" xmlns:si="http://www.siemens.com/OPCUA/2017/SimaticNodeSetExtensions" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:s1="http://opcfoundation.org/UA/Machinery/ProcessValues/Types.xsd" xmlns:s2="http://opcfoundation.org/UA/PADIM/Types.xsd" xmlns:ua="http://unifiedautomation.com/Configuration/NodeSet.xsd">
|
|
24
|
+
<NamespaceUris>
|
|
25
|
+
<Uri>http://opcfoundation.org/UA/DI/</Uri>
|
|
26
|
+
</NamespaceUris>
|
|
27
|
+
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
<UAObjectType IsAbstract="true" NodeId="ns=1;i=1003" BrowseName="1:BlockType">
|
|
31
|
+
<DisplayName>BlockType</DisplayName>
|
|
32
|
+
<Description>Adds the concept of Blocks needed for block-oriented FieldDevices</Description>
|
|
33
|
+
<Documentation>https://reference.opcfoundation.org/v105/DI/v103/docs/4.11</Documentation>
|
|
34
|
+
<References>
|
|
35
|
+
<Reference ReferenceType="HasSubtype" IsForward="false">ns=1;i=1001</Reference>
|
|
36
|
+
<Reference ReferenceType="HasProperty">ns=1;i=6009</Reference>
|
|
37
|
+
<Reference ReferenceType="HasProperty">ns=1;i=6010</Reference>
|
|
38
|
+
<Reference ReferenceType="HasProperty">ns=1;i=6011</Reference>
|
|
39
|
+
<Reference ReferenceType="HasProperty">ns=1;i=6012</Reference>
|
|
40
|
+
<Reference ReferenceType="HasProperty">ns=1;i=6013</Reference>
|
|
41
|
+
</References>
|
|
42
|
+
</UAObjectType>
|
|
43
|
+
|
|
44
|
+
...
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
From the XML we can see that the numeric identifier for the BlockType is `1003`, now in FactoryTalk Optix we need to read the NameSpaceIndex. We will also need the NamespaceUri for that CompanionSpecs, in this case is `http://opcfoundation.org/UA/DI/`
|
|
48
|
+
|
|
49
|
+
```csharp
|
|
50
|
+
[ExportMethod]
|
|
51
|
+
public void CreateCompanionSpecsInstance()
|
|
52
|
+
{
|
|
53
|
+
Log.Info("Create Companion Specs NetLogic", "Searching NameSpaceIndex for companion spec");
|
|
54
|
+
// Get the NameSpaceIndex URI from the XML file
|
|
55
|
+
var nameSpaceIndex = LogicObject.Context.GetNamespaceIndex("http://opcfoundation.org/UA/DI/");
|
|
56
|
+
if (nameSpaceIndex == -1 || nameSpaceIndex == null)
|
|
57
|
+
{
|
|
58
|
+
Log.Error("Create Companion Specs NetLogic", "NameSpaceIndex not found, make sure the CompanionSpecification was properly loaded");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
else
|
|
62
|
+
{
|
|
63
|
+
Log.Info("Create Companion Specs NetLogic", "NameSpaceIndex found: " + nameSpaceIndex);
|
|
64
|
+
}
|
|
65
|
+
// The ID of the Type we want to add is 1003 - we can get this value from the specs XML file
|
|
66
|
+
var companionSpecsTypeNodeId = new NodeId(nameSpaceIndex, 1003);
|
|
67
|
+
// Create an instance of the element
|
|
68
|
+
Log.Info("Create Companion Specs NetLogic", "Type definition NodeId: " + companionSpecsTypeNodeId);
|
|
69
|
+
var instanceName = InformationModel.Get(companionSpecsTypeNodeId).BrowseName + "Instance";
|
|
70
|
+
var companionSpecsInstance = InformationModel.MakeObject(instanceName, companionSpecsTypeNodeId);
|
|
71
|
+
Project.Current.Get($"Model/{instanceName}")?.Delete();
|
|
72
|
+
// Optional, set a value to a children variable set as "optional" in the CompanionSpecs
|
|
73
|
+
// companionSpecsInstance.GetOrCreateVariable("RevisionCounter").Value = 100;
|
|
74
|
+
Project.Current.Get("Model").Add(companionSpecsInstance);
|
|
75
|
+
Log.Info("Create Companion Specs NetLogic", $"Companion Specs instance \"{instanceName}\" created and added to the model");
|
|
76
|
+
}
|
|
77
|
+
```
|