32f26272ae
Five tools under one repo, all docs organized per DOCS-GUIDE.md: - aalogcli: .NET 4.8 / x86 CliFx CLI for reading System Platform binary logs (*.aaLGX) for LLM debugging, built on aaOpenSource/aaLog. Commands: last, tail, range, unread, fields. Stable JSON envelope under --llm-json. Build template under lib/build/ for rebuilding aaLogReader.dll. - aot: ArchestrA Object Toolkit 2014 v4.0 reference material. Dev guide (Markdown converted from CHM), API reference for the ArchestrA.Toolkit namespace, and the Monitor / Watchdog VS sample solutions. - graccesscli: .NET 4.8 / x86 CliFx CLI that automates Galaxy configuration via the ArchestrA GRAccess COM interop. Includes session daemon, IPC protocol, and llm-json envelope contract. - grdb: SQL/DDL exploration of the Galaxy Repository database. DDL captures, reusable queries, hierarchy / contained-name <-> tag-name translation notes. - histdb: LLM-oriented reference for AVEVA Historian retrieval. INSQL linked-server, extension tables, every wwXxx time-domain extension, every retrieval mode, alarm/event SQL recipes, REST API. Distilled from the 243-page Historian Retrieval Guide. Root contains: - CLAUDE.md: thin index pointing into each tool's README. - DOCS-GUIDE.md: doctrine for organizing docs for LLM consumption. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1072 lines
38 KiB
Markdown
1072 lines
38 KiB
Markdown
# Programming Techniques
|
||
|
||
Use the following workflow and programming techniques to code within the ArchestrA Object Toolkit (AOT).
|
||
|
||
## Programming Workflow
|
||
|
||
Using the AOT, you can seamlessly modify the object shape as the object is coded. The AOT also supports changing the ArchestrA attribute data type and renaming ArchestrA attributes and child primitives. This functionality, referred to as round-tripping, has been implemented by configuring the object shape in code. The code is parsed at build time to form the object’s aaDEF file that defines its shape.
|
||
|
||
Use the Object Designer to modify the the object shape code. The Designer parses the object shape from the code. Modifications made in the Object Designer are then written back to the code. The Object Designer also enables you to perform tasks that impact multiple code sections simultaneously, such as renaming ArchestrA attributes, adding local primitives, and making changes associated with modifying an object’s major version.
|
||
|
||
The basic steps of the workflow are:
|
||
|
||
1. Define the object shape using either the ArchestrA Object Toolkit Designer or in code directly.
|
||
2. Code the Configtime.
|
||
3. Code the Editor for the object.
|
||
4. Code the Runtime.
|
||
5. Build and import the object.
|
||
6. Test and debug the object.
|
||
|
||
> **Note**
|
||
> We recommend that you define the object shape with the ArchestrA Object Toolkit Designer and use code only when necessary. Refer to Advanced Techniques for information on coding the shape of the object.
|
||
|
||
## Configuring Internal and External Names
|
||
|
||
Internal names apply to objects, primitives, and attributes. Internal names are used in code and must be C# compliant.
|
||
|
||
Do not use .NET keywords, names of classes in the ArchestrA Object Toolkit, or names used in other libraries that are used in your code as internal names for objects, primitives, and attributes.
|
||
|
||
Internal names are used in code to refer to the primitive or attribute in C# through provided wrapper classes.
|
||
|
||
The maximum length of an object’s internal name is 255 characters.
|
||
|
||
The external name is used in the ArchestrA IDE and can be used to create default names for object instances. Each name should be meaningful and suggest the meaning of the object, primitive, or attribute.
|
||
|
||
External names must be ArchestrA compliant and cannot have any of the following characters: (space) + - * / \ = ( ) ` ~ ! % ^ & @ [ ] { } | : ; " , < > ? "
|
||
|
||
> **Note**
|
||
> For the sake of brevity, do not use the word "Object" or "Template" in an object's name.
|
||
|
||
## Providing Wrappers for Referencing ArchestrA Attributes
|
||
|
||
Attribute wrappers provide the following functionality for non-dynamic local attributes and non-virtual child primitive attributes:
|
||
|
||
- Strongly typed attribute references
|
||
- Automatic renaming of attribute references
|
||
- IntelliSense
|
||
|
||
> **Note**
|
||
> Renaming an attribute property causes Visual Studio to rename all references to the attribute in code except for string based references. For example, renaming Attribute1 does not modify “GetValue(“Attribute1”)”.
|
||
|
||
Modifying an attribute internal name using the Object Designer renames the Attribute property (known also as the attribute wrapper).
|
||
|
||
Using the Object Designer to modify the child primitive internal name, that is, the fully scoped name of the referenced attribute, also renames the Attribute property.
|
||
|
||
> **Note**
|
||
> Wrappers are updated each time the solution is parsed, that is, when the Object Designer is opened, the object is built, the Object Design View is refreshed, or Code Validate is selected.
|
||
|
||
## Config Time Coding
|
||
|
||
Use the following techniques to code for the Configtime project.
|
||
|
||
The Configtime project is used to provide all logic related to configuring the attributes of the object within the Galaxy database.
|
||
|
||
### Config Time Set Handler
|
||
|
||
Use the config time set handler to implement any logic required for setting the value of an attribute at configuration time.
|
||
|
||
This logic could include, for example, range checking and adding or removing virtual primitives.
|
||
|
||
You can add a Configtime set handler to an attribute by adding a set handler delegate and associated method to the Configtime class.
|
||
|
||
The Configtime class template provides commented out examples of a set handler delegate and method.
|
||
|
||
The set handler registration and associated methods are not renamed when the attribute is renamed in code. To remove the set handler, comment out or delete the registration. The Set Handler method can exist without the set handler registration; however, the method is not called.
|
||
|
||
In the following examples Attribute1 represents the internal and external name of the attribute.
|
||
|
||
The Configtime set handler is triggered when an attribute is changed at configuration time. It can be used for validation or to trigger a special action such as adding virtual primitives.
|
||
|
||
For additional information, see Configuring Run Time Set Handlers.
|
||
|
||
### Set Handler Code
|
||
|
||
Attribute1.SetHandlerValue enables the set handler code to appear the same for both array and non-array attributes. You can take more control of setting the value by using the following code examples:
|
||
|
||
- Non-array attribute:
|
||
|
||
```csharp
|
||
Attribute1 = e.Value;
|
||
|
||
```
|
||
|
||
- Array attributes:
|
||
|
||
```csharp
|
||
if (!e.IsArrayElement)
|
||
{
|
||
Attribute1 = e.Value;
|
||
}
|
||
else
|
||
{
|
||
Attribute1[e.attributeHandle.shIndex1] = e.Value;
|
||
}
|
||
```
|
||
|
||
### Performing Config Time Validation with the ConfigtimeValidate() Method
|
||
|
||
Use this method to validate the entire object as a whole when it is being saved. The method can put the object in a warning or error state by using EPACKAGESTATUS enum.
|
||
|
||
Example:
|
||
|
||
```csharp
|
||
private void AOTObject4Configtime_ConfigtimeValidate(object sender, ref EPACKAGESTATUS status)
|
||
{
|
||
// By default set the object status to Good
|
||
if (HiLimit < LoLimit)
|
||
{
|
||
status = EPACKAGESTATUS.ePackageBad;
|
||
AddErrorMessage("Hi Limit must be greater than or equal to Lo Limit");
|
||
}
|
||
else
|
||
{
|
||
status = EPACKAGESTATUS.ePackageGood;
|
||
}
|
||
}
|
||
```
|
||
|
||
### Adding a Virtual Primitive at Config Time with AddPrimitive
|
||
|
||
You can test the ability to add an instance of a virtual primitive at config time using the following function (Boolean result):
|
||
|
||
```csharp
|
||
bool CanAddPrimitive(string virtualPrimitiveName, string internalName, string externalName);
|
||
```
|
||
|
||
The following example uses CanAddPrimitive to check before adding a primitive:
|
||
|
||
```csharp
|
||
if (CanAddPrimitive("c1", "MyCP1InternalName", "MyCP1ExternalName"))
|
||
{
|
||
AddPrimitive("c1", "MyCP1InternalName", "MyCP1ExternalName");
|
||
}
|
||
```
|
||
|
||
Where:
|
||
|
||
- c1 is the internal name of the virtual primitive.
|
||
- MyCP1InternalName is the internal name of the Primitive Instance.
|
||
- MyCP1ExternalName is the external name of the primitive instance.
|
||
|
||
You can add an instance of a virtual primitive at config time and check the result using PrimitiveResult.message as shown in the following example:
|
||
|
||
```csharp
|
||
private void Attribute1SetHandler(object sender, ref ConfigtimeSetHandlerEventArgs e)
|
||
{
|
||
Attribute1.SetHandlerValue = e;
|
||
|
||
if (e.Value)
|
||
{
|
||
if (!AddPrimitive("c1", "MyCP1InternalName", "MyCP1ExternalName"))
|
||
{
|
||
e.Message = PrimitiveResult.message;
|
||
return; // Add failed
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Where:
|
||
|
||
- c1 is the internal name of the virtual primitive
|
||
- MyCP1InternalName is the internal name of the Primitive instance
|
||
- MyCP1ExternalName is the external name of the Primitive instance
|
||
- Attribute1 is a Boolean Attribute with a config time set handler.
|
||
|
||
> **Note**
|
||
> PrimitiveResult.message returns a message only on failure. To return status, use PrimitiveResult.status (EPRIMITIVEOPSTATUS).
|
||
|
||
Use to following example to iterate through the child primitives or child virtual primitives:
|
||
|
||
```csharp
|
||
object primitives;
|
||
this.Site.ChildVirtualPrimitives(ThisPrimitive, out primitives);
|
||
foreach (IPrimitiveShape ips in (IEnumerable)primitives)
|
||
{
|
||
LogInfo(ips.FullName);
|
||
}
|
||
```
|
||
|
||
### Removing a Virtual Primitive at Config Time with DeletePrimitive
|
||
|
||
You can test the ability to delete an instance of a virtual primitive at config time using the following function (Boolean result):
|
||
|
||
```csharp
|
||
bool CanDeletePrimitive(string internalName);
|
||
```
|
||
|
||
> **Note**
|
||
> CanDelete checks the lock status of the primitive being deleted.
|
||
|
||
Example:
|
||
|
||
```csharp
|
||
if (CanDeletePrimitive("MyCP1InternalName"))
|
||
{
|
||
DeletePrimitive("MyCP1InternalName");
|
||
}
|
||
```
|
||
|
||
Where:
|
||
|
||
MyCP1InternalName is the primitive instance being deleted.
|
||
|
||
You can delete an instance of a virtual primitive at config time and check the result using PrimitiveResult.message as shown in the following example:
|
||
|
||
```csharp
|
||
private void Attribute1SetHandler(object sender, ref ConfigtimeSetHandlerEventArgs e)
|
||
{
|
||
Attribute1.SetHandlerValue = e;
|
||
|
||
if (!e.Value)
|
||
{
|
||
if (!DeletePrimitive("MyCP1InternalName"))
|
||
{
|
||
e.Message = PrimitiveResult.message;
|
||
return; // Delete failed
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Where:
|
||
|
||
- c1 is the internal name of the virtual primitive.
|
||
- MyCP1InternalName is the internal name of the Primitive instance.
|
||
- MyCP1ExternalName is the external name of the primitive instance.
|
||
- Attribute1 is a Boolean attribute with a config time set handler.
|
||
|
||
> **Note**
|
||
> PrimitiveResult.message only returns a message on failure. To return status use PrimitiveResult.status (EPRIMITIVEOPSTATUS).
|
||
|
||
### Accessing Data in Attributes at Config Time
|
||
|
||
For static attributes, use attribute wrappers based on internal name to read/write value.
|
||
|
||
For dynamic attributes or attributes in a virtual primitive, use the GetValue and SetValue methods.
|
||
|
||
**Examples:**
|
||
|
||
If the static float attribute is named HiLimit:
|
||
|
||
```csharp
|
||
float myVal;
|
||
myVal = HiLimit;
|
||
```
|
||
|
||
If the dynamic float attribute is named HiLimit:
|
||
|
||
```csharp
|
||
float myVal;
|
||
myVal = GetValue(“HiLimit”);
|
||
```
|
||
|
||
### Accessing Data in Other Primitives at Config Time
|
||
|
||
For static primitives, use primitives and attributes wrappers based on internal name to read/write value.
|
||
|
||
For attributes in a virtual primitive, use the GetValue and SetValue methods.
|
||
|
||
**Examples:**
|
||
|
||
To access a float attribute with an internal name of HiLimit, of a static child primitive with the internal name Limits:
|
||
|
||
```csharp
|
||
float myVal;
|
||
myVal = Limits.HiLimit;
|
||
```
|
||
|
||
To access a float attribute with an external name of HiLimit, of an instance of a virtual child primitive with the external name Limits:
|
||
|
||
```csharp
|
||
float myVal;
|
||
myVal = GetValue(“Limits.HiLimit”);
|
||
```
|
||
|
||
### Adding and Deleting Dynamic Attributes at Config Time
|
||
|
||
Use the following code to add/remove a dynamic attribute.
|
||
|
||
```csharp
|
||
bool status = AddAttribute("dynAttr1", MxAttributeCategory.MxCategoryCalculated, MxDataType.MxDouble, false);
|
||
DeleteAttribute("dynAttr1");
|
||
|
||
```
|
||
|
||
For more information on these methods, see the *ArchestrA* *Object Toolkit Reference Guide*.
|
||
|
||
## Run Time Coding
|
||
|
||
Use the following techniques to code for run time. For more information on these methods, see the *ArchestrA Object* *Toolkit Reference Guide*.
|
||
|
||
### Runtime SetHandler
|
||
|
||
Use a run time set handler to implement any logic required to set an attribute value at run time, including range checking and accepting or rejecting the set.
|
||
|
||
You can add a run time set handler to an attribute by adding a set handler delegate and associated method to the Runtime class.
|
||
|
||
The Runtime class template provides commented out examples of a set handler delegate and method.
|
||
|
||
The set handler registration and associated methods are not renamed when the attribute is renamed in code. To remove the set handler, comment out or delete the registration. The Set Handler method can exist without the set handler registration; however, the method is not called.
|
||
|
||
In the following examples Attribute1 represents the attribute internal and external name.
|
||
|
||
The run time set handler registration appears in the Runtime class in the following region:
|
||
|
||
```csharp
|
||
#region Runtime Set Handler Registration - Toolkit generated code
|
||
#endregion Runtime Set Handler Registration
|
||
```
|
||
|
||
The run time Set Handler registration for Attribute1 appears as:
|
||
|
||
```csharp
|
||
this.RegisterRuntimeSetHandler("Attribute1.ex", new RuntimeSetHandlerDelegate(Attribute1SetHandler));
|
||
```
|
||
|
||
**Remarks**
|
||
|
||
The delegate is registered to the external name of the Attribute. In this example, the text "Attribute1.ex" represents the external name for Attribute1.
|
||
|
||
The run time set handler method for Attribute1 appears at the end of the Runtime class as:
|
||
|
||
```csharp
|
||
private void Attribute1SetHandler(object sender, ref RuntimeSetHandlerEventArgs e)
|
||
{
|
||
Attribute1.SetHandlerValue = e;
|
||
}
|
||
```
|
||
|
||
For additional information, see Configuring Run Time Set Handlers.
|
||
|
||
### Set Handler Code
|
||
|
||
Attribute1.SetHandlerValue enables the set handler code to appear the same for both array and non-array Aattributes. You can take more control of setting the value by using the following code examples:
|
||
|
||
- Non-array attribute:
|
||
|
||
```csharp
|
||
Attribute1 = e.Value;
|
||
|
||
```
|
||
|
||
- Array attributes:
|
||
|
||
```csharp
|
||
if (!e.IsArrayElement)
|
||
{
|
||
Attribute1 = e.Value;
|
||
}
|
||
else
|
||
{
|
||
Attribute1[e.attributeHandle.shIndex1] = e.Value;
|
||
}
|
||
```
|
||
|
||
#### SetInfo Structure Event Arguments
|
||
|
||
Event arguments to provide the run time set handler event with the required data:
|
||
|
||
```csharp
|
||
public class RuntimeSetHandlerEventArgs : SetHandlerEventArgs
|
||
{
|
||
// an attribute to pass in the Set Info:
|
||
SetInfo attributeInfo;
|
||
|
||
// an attribute to pass out the status:
|
||
MxStatus status;
|
||
|
||
// Constructor
|
||
RuntimeSetHandlerEventArgs(AttributeHandle pAttributeHandle, SetInfo pInfo, MxStatus _status, IMxValue pMxValue);
|
||
}
|
||
```
|
||
|
||
### Coding a RuntimeExecute() Method
|
||
|
||
Use this method to get inputs, perform calculations, set outputs, and set alarm Booleans.
|
||
|
||
> **Note**
|
||
> This is the most important run time method, but it needs to be efficient and not time-consuming, or it could cause scan overruns.
|
||
|
||
**Example:**
|
||
|
||
If the object is called Test:
|
||
|
||
Test_RuntimeExecute()
|
||
|
||
### Returning an Error Status String at Run Time
|
||
|
||
**Use the RuntimeGetStatusDesc method to return an error message string associated with a previously returned error code from a sethandler.**
|
||
|
||
**Example:**
|
||
|
||
```csharp
|
||
private void xxx_RuntimeGetStatusDesc(object sender, ref RuntimeGetStatusDescEventArgs e)
|
||
{
|
||
//----------------------------------------------
|
||
|
||
// TODO: Runtime Event - GetStatusDesc
|
||
//
|
||
// This routine provides a String for an
|
||
// error code when a client requests it.
|
||
// By default this method looks for an entry
|
||
// in the dictionary that has the
|
||
// DetailedErrorCode as the PhraseID.
|
||
//
|
||
// You need to change this implmentation if
|
||
// you want to provide embedded values
|
||
// within your messages, or you want to use
|
||
// string PhraseIDs instead of integer
|
||
// PhraseIDs.
|
||
//----------------------------------------------
|
||
|
||
switch (e.detailedErrorCode)
|
||
{
|
||
default:
|
||
e.status = GetText((int)e.detailedErrorCode);
|
||
break;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### RuntimeGetStatusDesc Event
|
||
|
||
Delegates added to this event are called when the event is fired by ArchestrA:
|
||
|
||
```csharp
|
||
event RuntimeGetStatusDescDelegate RuntimeGetStatusDesc;
|
||
```
|
||
|
||
### Event Handler for Get Status Description
|
||
|
||
Use the following to provide the GetStatusDescription event with the required data:
|
||
|
||
```csharp
|
||
class RuntimeGetStatusDescEventArgs : EventArgs
|
||
{
|
||
// an attribute to pass out the detailed error code:
|
||
short detailedErrorCode;
|
||
|
||
// an attribute to pass out the status:
|
||
string status;
|
||
|
||
// Constructor:
|
||
RuntimeGetStatusDescEventArgs();
|
||
}
|
||
```
|
||
|
||
### Manipulating Data Quality at Run Time
|
||
|
||
Use code to read data quality from and write data quality to attributes.
|
||
|
||
**Example:**
|
||
|
||
```csharp
|
||
myAttribute = Input1.Value.Value;
|
||
myAttribute.Quality = Input1.Value.Quality;
|
||
|
||
if( myAttribute.Quality.isBad )
|
||
{
|
||
// then do something like set alarm
|
||
...
|
||
}
|
||
```
|
||
|
||
### Manipulating the Timestamp at Run Time
|
||
|
||
Use code to read and write the time stamp at run time.
|
||
|
||
**Example:**
|
||
|
||
```csharp
|
||
myAttribute = Input1.Value.Value;
|
||
myAttribute.Time = Input1.Value.Time;
|
||
```
|
||
|
||
### Getting Input (I/O) Values Using Utility Primitives at Run Time
|
||
|
||
Use code to read input value, status and quality from Input or InputOutput utility primitives. Use wrapper classes if the utility primitive is static; otherwise, use GetValue.
|
||
|
||
**Examples:**
|
||
|
||
Input primitive:
|
||
|
||
```csharp
|
||
myAttribute = Input1.Value.Value;
|
||
CMxStatus myStatus = Input1.ReadStatus;
|
||
```
|
||
|
||
InputOutput primitive:
|
||
|
||
```csharp
|
||
myAttribute = InputOutput1.ReadValue;
|
||
CMxStatus myStatus = InputOutput1.ReadStatus;
|
||
myAttribute.Quality = InputOutput1.ReadValue.Quality;
|
||
myAttribute.Time = InputOutput1.ReadValue.Time;
|
||
```
|
||
|
||
### Setting Output (I/O) Values Using Utility Primitives at Run Time
|
||
|
||
Code utility primitives to write output value and read status from Output or InputOutput Utility Primitive. Use wrapper classes if the Utility Primitive is static; otherwise, use SetValue.
|
||
|
||
**Examples:**
|
||
|
||
Output primitive:
|
||
|
||
```csharp
|
||
Output1.Value = myValue;
|
||
Output1.Value.Time = myTime;
|
||
```
|
||
|
||
InputOutput primitive:
|
||
|
||
```csharp
|
||
InputOutput1.WriteValue = myValue;
|
||
InputOutput1.WriteValue.Time = myTime;
|
||
```
|
||
|
||
Writing to the Quality of the Input, Output, or InputOutput primitive wrapper is not supported. The Quality of Value, WriteValue, and ReadValue is read-only. For example, attempting to set InputOutput1.WriteValue.Quality = SomeAttribute.Quality may result in an erroneous value (InputOutput1.WriteValue.Value) to be written to the output location.
|
||
|
||
### Accessing Data in Attributes at Run Time
|
||
|
||
For static attributes, use attribute wrappers based on the internal name to read or write values, quality, and time stamps.
|
||
|
||
**Example:**
|
||
|
||
If attribute is called Attribute1, just use Attribute1 in code.
|
||
|
||
```csharp
|
||
int i = Attribute1;
|
||
```
|
||
|
||
For dynamic attributes or attributes in a virtual primitive, use the GetValue and SetValue methods.
|
||
|
||
**Example:**
|
||
|
||
For virtuals and dynamics:
|
||
|
||
```csharp
|
||
GetValue("attribute1");
|
||
```
|
||
|
||
or
|
||
|
||
```csharp
|
||
GetValue( primitiveId, attributeId);
|
||
```
|
||
|
||
See the AObjectBase class definition for further details on the GetValue member.
|
||
|
||
### Accessing Data in Other Primitives at Run Time
|
||
|
||
For static primitives, use primitives and attributes wrappers based on the internal name to read/write value, quality, and time stamp.
|
||
|
||
For attributes in a virtual primitive, use the GetValue and SetValue methods.
|
||
|
||
**Examples:**
|
||
|
||
```csharp
|
||
prim1.attribute1 = 23.0; // for static primitives and attributes
|
||
int i = GetValue("prim1.attribute1"); // for dynamic primitives and attributes
|
||
SetValue("prim1.attribute1",23.0); // for dynamic primitives and attributes
|
||
```
|
||
|
||
### Adding and Deleting Dynamic Attributes at Run Time
|
||
|
||
Use the following code to add/remove a dynamic attribute in run time code.
|
||
|
||
```csharp
|
||
bool status = AddAttribute("dynAttr1", MxAttributeCategory.MxCategoryCalculated, MxDataType.MxDouble, false);
|
||
DeleteAttribute("dynAttr1");
|
||
```
|
||
|
||
### Supporting AdviseOnlyActive at Run Time
|
||
|
||
Application Server can suspend the input polling required for an attribute when that attribute is not currently being used, such as when it is not being viewed by a client, alarmed, script-referenced, or historized. For more information on the AddAttribute methods and overloads, see the *ArchestrA* *Object Toolkit Reference Guide*.
|
||
|
||
The Advise Only Active feature of the run time infrastructure determes whether an attribute is currently being used or not, and can suspend and activate that attribute at the appropriate time.
|
||
|
||
The ApplicationObject must determine which attributes are eligible candidates to be suspended when not being used. The ApplicationObject must also turn off the input polling required for an attribute when suspended, and turn the input polling back on when the attribute is activated.
|
||
|
||
To enable AdviseOnlyActive:
|
||
|
||
1. Determine whether or not the Object should support Advise Only Active functionality. If so, enable the **Advise** **Only Active supported** check box in the Object Editor.
|
||
2. Determine for each primitive being developed what attributes are eligible for Advise Only Active functionality. These typically will only be attributes that are updated or associated with “live” updates from external sources, usually from input type primitives. They can also be subscriptions using MX.
|
||
3. In either the code or the object editor, set “Advise Only Active” in the selected attributes to True.
|
||
|
||
> **Note**
|
||
> In the Runtime Startup method, the auto-generated code checks whether AdviseOnlyActiveEnabled is enabled for the object. If AdviseOnlyActiveEnabled is enabled , the auto-generated code calls SuspendLocalAttribute() for each attribute supporting Advise Only Active.
|
||
|
||
1. Implement the body of the provided AttributeName_AdviseOnlyActive() method for each attribute supporting Advise Only Active:
|
||
a. The method shell is auto-generated.
|
||
b. Fill in code in this method to take necessary actions to activate or suspend updates of polled data related to the specified attribute being activated. Typically, an Input primitive or InputOutput primitive wrapper is called, such as:
|
||
|
||
```csharp
|
||
|
||
Input1.ActivateUpdatesList()
|
||
|
||
InputOutput1.ActivateUpdatesList()
|
||
```
|
||
|
||
1. You can also choose to take other actions, including:
|
||
a. Activate/suspend updates on an attribute in another object using CMxIndirect.Activate() or CMxIndirect.Suspend().
|
||
b. Activate/suspend updates on an attribute in another primitive using the ActivatePrimitiveAttribute() or SuspendPrimitiveAttribute() methods.
|
||
|
||
> **Note**
|
||
> In Runtime Shutdown method, if AdviseOnlyActiveEnabled is enabled, the auto-generated code calls ActivateLocalAttribute() for each attribute supporting AdviseOnlyActive.
|
||
|
||
#### AdviseOnlyActiveEnabled
|
||
|
||
Use this method or property to determine whether the object has AdviseOnlyActive functionality enabled. If disabled, the object must not call functions to use AdviseOnlyActive. The AOT prevents functions such as SuspendLocalAttribute() from being used if AdviseOnlyActive functionality is disabled. This method or property determines if AdviseOnlyActive functionality is enabled.
|
||
|
||
### Other AOT Wrappers for AdviseOnlyActive
|
||
|
||
In addition to the wrappers indicated in the previous section, the AOT adds the following wrapper function for AdviseOnlyActive.
|
||
|
||
#### IO Utilities
|
||
|
||
Input and InputOutput utility primitives class wrappers provide methods to SuspendUpdatesList() and ActivateUpdatesList() that suspend and activate the input polling for the utility primitive.
|
||
|
||
### Triggering an Alarm at RunTime
|
||
|
||
Set the Boolean attribute representing the alarm to True to trigger the alarm. Set the attribute to False to clear the alarm condition.
|
||
|
||
**Example:**
|
||
|
||
```csharp
|
||
myCondition = true;
|
||
```
|
||
|
||
> **Note**
|
||
> The Boolean attribute must have an alarm extension added to it in the Object Designer.
|
||
|
||
## Providing Access to External Attributes (BindTo)
|
||
|
||
The BindTo method of RuntimeBase provides a simplified method for accessing attributes in other objects at run time using CMxIndirect. For more information, see CMxIndirect.
|
||
|
||
You can use BindTo to:
|
||
|
||
- Read the value, time, and quality of an attribute or property.
|
||
- Set the value and time of an attribute. Quality cannot be set.
|
||
|
||
The Value, Quality, Datatype, Length, and Time of the external attribute can be accessed by the properties:
|
||
|
||
```csharp
|
||
myIndirect.Value
|
||
myIndirect.DataQualityOfLastRead
|
||
myIndirect.Value.GetDataType
|
||
myIndirect.Value.Length
|
||
myIndirect.TimeStampOfLastRead
|
||
```
|
||
|
||
Like value, time can be set across object boundaries. You can access time without having to access value, but you must do this by binding to the Time property directly.
|
||
|
||
**Example:**
|
||
|
||
```csharp
|
||
myIndirect = RuntimeBase.BindTo( “obj.attr.time”, “”);
|
||
myIndirect.Value = DateTime.Now();
|
||
myTime = myIndirect.Value; // where myTime is a System.DateTime variable
|
||
```
|
||
|
||
You can set both time and value together in one call:
|
||
|
||
```csharp
|
||
myIndirect.Set( x, myTime );
|
||
```
|
||
|
||
Value and time can be set together as a pair.
|
||
|
||
**Example:**
|
||
|
||
```csharp
|
||
private CMxIndirect myIndirect = null;
|
||
.
|
||
.
|
||
.
|
||
myIndirect = BindTo("MyTestObject.Attribute1", "", true);
|
||
if (myIndirect != null )
|
||
{
|
||
myIndirect.Set( 180, DateTime.Now() ); // sets V, T in one call
|
||
}
|
||
```
|
||
|
||
The developer can get both Time and Value together in one call:
|
||
|
||
```csharp
|
||
if (myindirect.StatusOfLastRead.success == -1 && myindirect.StatusOfLastRead.Category == MxStatusCategory.MxCategoryOk)
|
||
{
|
||
myIndirect.Get( out x, out myTime, out myQuality );
|
||
}
|
||
```
|
||
|
||
After the BindTo operation, check the status before accessing the value as shown in the previous example.
|
||
|
||
> **Note**
|
||
> Declaring the CMxIndirect in the Runtime Declarations Section makes the indirect available to all methods in the Runtime, that is, to Startup and Execute.
|
||
|
||
### CMxIndirect
|
||
|
||
Used for referencing external attributes. For more information, see Providing Access to External Attributes (BindTo).
|
||
|
||
```csharp
|
||
public class CMxIndirect
|
||
{
|
||
public CMxIndirect(string _fullRefString, string _context, IMxSupervisoryConnection _superConn, RuntimeBase _rb, int _refHandle, short _statusId, int _statusIndex);
|
||
|
||
public MxStatus CallBackStatus { get; }
|
||
public string Context { get; }
|
||
public short DataQuality { get; }
|
||
public string FullReferenceString { get; }
|
||
public bool IsGood { get; }
|
||
public int RefHandle { get; }
|
||
public MxStatus Status { get; }
|
||
public short StatusId { get; }
|
||
public int StatusIndex { get; }
|
||
public CMxValue Value { get; set; }
|
||
public CMxTime Time { get; set; }
|
||
public void Set { CMxValue value, CMxTime time };
|
||
public void Get { out CMxValue value, out CMxTime time, out short quality };
|
||
}
|
||
```
|
||
|
||
## Associating an ArchestrA Editor Control with an Attribute in Code
|
||
|
||
Normally, ArchestrA editor controls are statically configured to point to a single attribute to be configured on the editor tab.
|
||
|
||
However, on occassion, and especially with virtual primitives, it is useful to dynamically bind the editor control to a particular attribute by setting the Attribute property of the control.
|
||
|
||
For example, with a text box control, the following shows how to set the attribute name in code:
|
||
|
||
```csharp
|
||
aaTextBox1.Attribute = "myAttribute1";
|
||
```
|
||
|
||
### Referencing Attributes Using GetValue and SetValue
|
||
|
||
The AOT enables the use of SetValue and GetValue to reference the following types of attributes:
|
||
|
||
- Child primitive attributes
|
||
- Dynamic attributes
|
||
- Virtual primitive attributes
|
||
|
||
You can reference these attributes using SetValue and GetValue with a relative string reference. The string reference is relative to the primitive that contains the code, and can be prefixed with the following modifiers:
|
||
|
||
- “me”
|
||
- “myparent”
|
||
- “myobject”
|
||
|
||
> **Note**
|
||
> The prefix “me” is implied when no prefix is provided.
|
||
|
||
The GetValue and SetValue methods use the primitive external name and attribute external name as shown in the following example:
|
||
|
||
```csharp
|
||
GetValue(PrimitiveExternalName.AttributeExternalName)
|
||
```
|
||
|
||
Examples included in this section are based on the following hierarchy:
|
||
|
||
### Local References
|
||
|
||
To reference any local attributes, use the attribute name with no prefixes or scope.
|
||
|
||
Using the me prefix explicitly sets the reference to local and can be used to make it clear what type of reference is required.
|
||
|
||
Either of the following statements in Child2 gets the value of AOTObjectx1.Child2.Attribute1:
|
||
|
||
```csharp
|
||
GetValue("Attribute1");
|
||
GetValue("me.Attribute1");
|
||
```
|
||
|
||
### Referencing Down (child)
|
||
|
||
To reference attributes of a child primitive, prefix the reference string with the name of the child primitives.
|
||
|
||
The following statement in Child2 gets the value of AOTObjectx1.Child2.Child4.Attribute1:
|
||
|
||
```csharp
|
||
GetValue("Child4.Attribute1");
|
||
```
|
||
|
||
The following statement in AOTobjectx1 gets the value of AOTObjectx1.Child2.Child4.Attribute1:
|
||
|
||
```csharp
|
||
GetValue("Child2.Child4.Attribute1");
|
||
```
|
||
|
||
### Referencing Up (parent)
|
||
|
||
To reference attributes of a parent primitive use the myparent prefix.
|
||
|
||
The following statement in Child2 gets the value of AOTObjectx1.Attribute1:
|
||
|
||
```csharp
|
||
GetValue("myparent.Attribute1");
|
||
```
|
||
|
||
The following statement in Child4 gets the value of AOTObjectx1.Child2.Attribute1
|
||
|
||
```csharp
|
||
GetValue("myparent.Attribute1");
|
||
```
|
||
|
||
The myparent prefix cannot be used more than once in a reference. To reference attributes of objects or primitives more than one level higher, you must use myobject to locate the reference to the top of the hierarchy and then work relative to that location.
|
||
|
||
The following statement in Child4 gets the value of AOTObjectx1.Attribute1:
|
||
|
||
```csharp
|
||
GetValue("myobject.Attribute1");
|
||
```
|
||
|
||
The following statement in Child4 gets the value of AOTObjectx1.Child1.Child3.Attribute1:
|
||
|
||
```csharp
|
||
GetValue("myobject.Child1.Child3.Attribute1");
|
||
```
|
||
|
||
### Array Usage
|
||
|
||
```csharp
|
||
// Increment the 3rd element of an array
|
||
FloatArray1[3]++;
|
||
// Increment all the elements of an array
|
||
for (short counter = 1; counter <= FloatArray2.Length; counter++)
|
||
{
|
||
FloatArray2[counter]++;
|
||
}
|
||
```
|
||
|
||
## The External Build Process
|
||
|
||
The build process is made up of many stages. In general, these stages all occur as a single event.
|
||
|
||
Sometimes you may want to execute only part of the build process or to add additional events in the middle of the process. To support this, you can start a solution build from the command line as well as repackage the object with the Packager application.
|
||
|
||
> **Note**
|
||
> The project must be built before you execute the Packager, because the Packager requires the aaDEF file and associated assemblies create by the build.
|
||
|
||
### Command Line Recompile Object
|
||
|
||
Perform these processes from a command prompt with the Visual Studio Environment variables loaded.
|
||
|
||
**Build Process - Recompile (debug version)**
|
||
|
||
```csharp
|
||
C:\>devenv "C:\ Projects\AOTObject88\AOTObject88.sln" /build Debug
|
||
```
|
||
|
||
**Build Process - Recompile (release version)**
|
||
|
||
```csharp
|
||
C:\>devenv "C:\ Projects\AOTObject88\AOTObject88.sln" /build Release
|
||
```
|
||
|
||
### Command Line Repackage Object
|
||
|
||
The AOT includes a utility called Packager.
|
||
|
||
The Packager packages the object using the files created by the build, that is, it packages the aaDEF files and assemblies created by the build. This allows a build process to repackage the aaPDF after the object is rebuilt using the command line build.
|
||
|
||
You can start the Packager as a Windows Form application or execute it from the command line using the following switches:
|
||
|
||
/q - command line mode, no Form.
|
||
|
||
/f <filename> - The name of a text file containing information needed by DesignServer to repackage the Object.
|
||
|
||
The file is automatically generated in the Output directory when the AOT builds the object using the name DesignServerInfo.txt. It contains the name of the root aaDEF file and all of the paths to the core object dlls as relative paths, that is, the project is portable.
|
||
|
||
**Example**
|
||
|
||
```csharp
|
||
C:\Program Files\ArchestrA\Toolkits\AOT\Bin>packager /q /f "C:\ Projects\AOTObject90\Output\designserverinfo.txt" /a "C:\Utility Dlls\"
|
||
```
|
||
|
||
> **Note**
|
||
> This feature allows you to modify the aaDEF file and repackage the Object.
|
||
|
||
## Advanced Techniques
|
||
|
||
Use these techniques to configure an object or primitive completely in code using C# attributes. Using the integrated Object Desiger causes these C# attributes declarations to be automatically added to the code.
|
||
|
||
### Configuring an ArchestrA Attribute in Code
|
||
|
||
You can add ArchestrA attributes to the object. They can be used locally in code as C# variables.
|
||
|
||
The ArchestrA attributes are declared in the object project as C# variables using a CMx Data Type.
|
||
|
||
When the object is parsed, properties are automatically added to the Runtime and Configtime class for each ArchestrA attribute defined in the Object class. Parsing occurs while:
|
||
|
||
- Refreshing the AOT Object Design View
|
||
- Opening the AOT Object Designer
|
||
- Saving an object in the AOT Object Designer
|
||
- Executing build or code validation
|
||
|
||
The attribute properties allow you to access ArchestrA attributes as though they were strongly typed C# values.
|
||
|
||
When you reference the ArchestrA attribute in code, the property provides a C# typed wrapper to the attribute using SetValue and GetValue access. The wrapper provides access to Quality, Time, Data Type, and Array Length.
|
||
|
||
The following table lists the data types and associated C# conversions.
|
||
|
||
| ArchestrA Data Type | C# Data Type |
|
||
| --- | --- |
|
||
| Boolean | bool |
|
||
| Integer | int |
|
||
| Float | float |
|
||
| Double | double |
|
||
| Time | DateTime |
|
||
| Elapsed Time | TimeSpan |
|
||
| String | string |
|
||
| Big String | string |
|
||
| Attribute Reference | string |
|
||
| Custom Enumeration | string array |
|
||
| DataType | ArchestrA.Core.MxDataType |
|
||
| Custom Structure | ArchestrA.Core.MxCustomStruct |
|
||
| MxStatus | ArchestrA.Core.MxStatus |
|
||
| Variant | CMxValue |
|
||
| InternalDumpLoadData | ArchestrA.Core.MxCustomStruct (special) |
|
||
| InternalFailoverData | ArchestrA.Core.MxCustomStruct (special) |
|
||
|
||
> **Note**
|
||
> There is no direct conversion from an internationalized string to a C# type. The internationalized string can be represented as an internationalized string structure (string value, locale) or by setting the locale for the attribute, which then allows you to reference the attribute as a C# string.
|
||
|
||
The variable declaration can be decorated with C# attributes to enable the configuration of the following attribute properties (excluding special data types):
|
||
|
||
- External Name
|
||
- Category
|
||
- Security
|
||
- Calculated Quality and Time
|
||
- Frequently Accessed
|
||
- Alarm Extension
|
||
- History Extension
|
||
|
||
> **Note**
|
||
> The special data types of CMxInternalDumpLoadData and CMxInternalFailoverData are created and maintained by the toolkit. These data types are not intended for general use. These ArchestrA attributes store data for recreating dynamic attributes and child primitive instances on dump/load and failover.
|
||
|
||
#### Specifying the ArchestrA Attribute Array Length
|
||
|
||
You can set and get the array length of an attribute array (static) at config time or run time using the following syntax:
|
||
|
||
**Syntax**
|
||
|
||
```csharp
|
||
AttributeName.Length = n;
|
||
n = AttributeName.Length;
|
||
```
|
||
|
||
**Parameters**
|
||
|
||
AttributeName
|
||
|
||
Represents the array attribute name.
|
||
|
||
n
|
||
|
||
Represents an integer value.
|
||
|
||
**Remarks**
|
||
|
||
You can set the array length of a dynamic attribute array at config time or run time using one of the following Get and Set methods:
|
||
|
||
```csharp
|
||
n = GetNumElements("AttributeName");
|
||
n = GetNumElements(AttributeID, PrimitiveID);
|
||
SetNumElements("AttributeName", n);
|
||
SetNumElements(AttributeID, PrimitiveID, n);
|
||
```
|
||
|
||
Where, AttributeName is the array attribute name and n is an integer value.
|
||
|
||
You can apply the GetNumElements and SetNumElements methods to attributes. However, when you rename an attribute using the Object Designer, the attribute name referenced by these methods is not updated.
|
||
|
||
### Referencing Attributes from the Editor of the Object
|
||
|
||
To reference attributes directly from the Editor Project of the object, you must implement the GetData() and SetData() methods provided by the framework.
|
||
|
||
Even if the config-time dynamic attribute is located on OnObject, you cannot use Editor functions SetData and GetData, and Runtime functions Set and Get, if the attribute reference contains an object name. For example, **myTestAOT_001.myuda**.
|
||
|
||
For more information, see Enabling Buffered Data for a Config-time Dynamic Attribute.
|
||
|
||
In the examples, Attribute1 is a float array with four elements, Attribute2 represents is a float.
|
||
|
||
**Attribute Get Example:**
|
||
|
||
```csharp
|
||
//Get an Attribute value
|
||
float MyData2 = (float)Convert.ToDecimal(GetData("Attribute2"));
|
||
```
|
||
|
||
**Attribute Set Example:**
|
||
|
||
```csharp
|
||
//Set an Attribute value
|
||
SetData("Attribute2", 9.0);
|
||
```
|
||
|
||
**Array Attribute Get Example:**
|
||
|
||
```csharp
|
||
//Get a single value of an Array Attribute
|
||
float MyData = (float)Convert.ToDecimal(GetData("Attribute1[1]"));
|
||
//Get an Array Attribute
|
||
object[] MyArrayValues = new object[4];
|
||
MyArrayValues = (object[])GetData("Attribute1");
|
||
```
|
||
|
||
**Array Attribute Set Example**
|
||
|
||
```csharp
|
||
//Set an Array Attribute using a locally declared array
|
||
object[] MyArrayValues = new object[4];
|
||
MyArrayValues[0] = 1.0;
|
||
MyArrayValues[1] = 1.1;
|
||
MyArrayValues[2] = 1.2;
|
||
MyArrayValues[3] = 1.3;
|
||
SetData("Attribute1", MyArrayValues);
|
||
```
|
||
|
||
> **Note**
|
||
> The array index is 1-based. There are no errors or warnings to indicate that a zero value has been passed to the array index from the editor of the object.
|
||
|
||
### Local Attribute Wrappers
|
||
|
||
Based on the attribute category, AOT adds an Attribute property to the Configtime class or the Runtime class for each attribute declared in the Object class. You can use the local attribute wrapper to access attributes using the attributes name attribute_internalname.
|
||
|
||
The following code example represents an auto-generated Attribute property added to the Configtime or Runtime class for an attribute that supports read and write. In the example, the Set statement would be excluded if the attribute were read-only.
|
||
|
||
```csharp
|
||
private CMxBoolean Attribute1
|
||
{
|
||
get { return InternalReferenceOnly.Attribute1; }
|
||
set { InternalReferenceOnly.Attribute1.Set(value); }
|
||
}
|
||
```
|
||
|
||
The get property provides access to the attribute wrapper and allows you to access the features of the wrapper, such as quality and security.
|
||
|
||
The set property is limited to setting the value. This is how the property is used when the attribute is on the left side of an assignment operator, for example, Attribute1 = 10. You can assign values using this short method. It provides type checking and automatic type conversion of the value.
|
||
|
||
The property does not provide any other set access to the wrapper.
|
||
|
||
The following table shows the relationship between the attribute category and the attribute properties Get and Set added to the Configtime and Runtime classes.
|
||
|
||
| Attribute Category | Configtime Class | Runtime Class |
|
||
| --- | --- | --- |
|
||
| PackageOnly | Get and Set | - - |
|
||
| PackageOnly_Lockable | Get and Set | - - |
|
||
| Constant | Get Only | Get Only |
|
||
| Writeable_C_Lockable | Get and Set | Get Only |
|
||
| Writeable_UC | Get and Set | Get and Set |
|
||
| Writeable_UC_Lockable | Get and Set | Get and Set |
|
||
| Writeable_USC | Get and Set | Get and Set |
|
||
| Writeable_USC_Lockable | Get and Set | Get and Set |
|
||
| Calculated | - - | Get and Set |
|
||
| Calculated_Retentive | - - | Get and Set |
|
||
| Writeable_S | - - | Get and Set |
|
||
| Writeable_U | - - | Get and Set |
|
||
| Writeable_US | - - | Get and Set |
|
||
| SystemInternal | Unsupported | Unsupported |
|
||
| SystemSetsOnly | Unsupported | Unsupported |
|
||
| SystemWriteable | Unsupported | Unsupported |
|