Files
wwtools/aot/dev-guide/appendix-a-best-practices.md
Joseph Doherty 32f26272ae Initial commit: Wonderware / System Platform tools and reference
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>
2026-05-03 18:22:20 -04:00

260 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Development Best Practices
When developing your object, you should follow certain guidelines to ensure correct functionality and to avoid common pitfalls. See the following sections for guidelines and tips on developing config time code, run time code, and the custom object editor.
## General Guidelines
Use the following general guidelines when developing your object.
### Naming Conventions
Use attribute and primitive names that are consistent within your object and with other objects in the ArchestrA environment. This makes it easier for operators and system engineers to browse the ArchestrA object namespace.
#### Naming Restrictions
- The following characters are invalid in ArchestrA names: (space) . + - * / \ = ( ) ` ~ ! % ^ & @ [ ] { } | : ; " , < > ?
- Non-English (“localized”) characters are supported in the external names of attributes, objects and primitives, but not in their internal names.
- You can use periods to create a logical naming hierarchy for attributes and primitives (see Creating a Logical Attribute Hierarchy). The maximum length of each identifier between periods is 32 characters. The maximum length of the entire name including all identifiers is 329 characters for attributes and 255 characters for primitives.
#### ArchestrA Naming Standards and Abbreviations
The following table lists a set of standards for naming the attributes and primitives of ArchestrA objects. While you may not be able to apply it universally, you should follow it whenever possible to promote consistency across ArchestrA objects.
| Instead of | Use | Comment |
| --- | --- | --- |
| acknowledge | Ack | |
| acknowledged | Acked | |
| Address | Addr | |
| Alarm | Alarm | Dont abbreviate. |
| Attribute | attr | |
| Automatic | auto | |
| Average | avg | |
| Cascade | casc | |
| command | cmd | OK to spell out “commandable” and “commanded.” |
| configuration | config | OK to spell out “configure” or “configured.” |
| connection | connection | Dont abbreviate. |
| Control | ctrl | |
| controller | ctrlr | |
| Count | cnt | |
| dataaccess | DA | |
| description | desc | |
| destination | dest | |
| deviation | dev | |
| Different | diff | |
| Directory | dir | |
| dynamicdataexchange | DDE | |
| engineeringunits | EngUnits | |
| Enum | enumerationset | |
| EU | EngUnits | Use “EngUnits” |
| evaluation | eval | |
| External | external | Dont abbreviate. |
| GloballyUniqueID | GUID | All uppercase |
| High | hi | |
| Identifier | id | |
| Interval | period | Dont use “interval” to specify a time between cyclic events. Use “period” instead. |
| Low | lo | |
| Manual | man | |
| maximum | max | |
| message | msg | |
| Minimum | min | |
| mxreference | reference | |
| Number | cnt | “Cnt” is short for “count.” You can use “Number” if it refers to an index, not a count. For example, “TelephoneNumber” is OK because it specifies a literal number, not a count of telephones. |
| Object | object | Dont abbreviate. |
| Output | OP | Abbreviation only used for PID controller |
| password | password | Dont abbreviate. |
| processvalue | PV | |
| Put | set | Use “set” instead of “put.” |
| Queue | queue | Dont abbreviate. |
| randomaccessmemory | RAM | |
| rateofchange | ROC | |
| Received | rcvd | |
| reference | reference | Dont abbreviate. |
| Server | server | For attribute names, dont abbreviate. For file names, OK to abbreviate to “svr.” |
| Setpoint | SP | Abbreviation only used for PID controller |
| Solicit | solicit | Dont abbreviate. |
| Statistics | stats | |
| userdefinedattribute | UDA | |
| Value | value | Dont abbreviate. |
#### Additional Naming Guidelines
- When a name contains multiple words, **begin each** **word with a capital letter**. For example, “Average Page Faults” becomes “PageFaultsAvg.” When one of the words itself is an acronym (for example, “CPU”), still capitalize the word following the acronym. For example, “CPU Load” becomes “CPULoad,” not “CPUload.”
- **Place adjectives after the noun.** This causes objects of interest (typically, the noun) to be grouped together in an alphabetical list. For example, use “FlowAvg,” “FlowMax,” and “FlowMin” instead of “AvgFlow,” “MaxFlow,” and “MinFlow.”
- Use **plural names for** attributes that are **arrays**.
- **Avoid unnecessary adjectives** when the noun itself is understood. For example, for an attribute that indicates the CPU load, dont use “CPULoadCurrent” or the abbreviated “CPULoadCur” when “CPULoad” is enough.
### Creating a Logical Attribute Hierarchy
An objects primitives naturally create a hierarchical namespace of attributes. Every attribute has a Hierarchical Name that includes the external name of the primitive that contains it. Without care, this namespace may expose the underlying primitive structure of the object to end users, which is usually undesirable from a useability standpoint.
You can use two strategies to address this issue: unnamed primitives, and periods in attribute names.
#### Using "Unnamed" Primitives
When appropriate, primitives can be “unnamed,” that is, their external name is empty. This causes all of the primitives attributes to appear to belong to the primitives container (either the parent primitive or the object itself).
#### Using Periods in Attribute Names
By using a period in an attribute name, you can create a hierarchy within the object namespace that is independent of the objects primitive structure.
This is recommended when an attribute is related to a contained primitive. In these situations, the name of the attribute should always be the same as the contained primitives name, or extend the contained primitives name using a period.
For example, if your object includes an alarm primitive named “AlarmHiHi,” you could create an object attribute named “AlarmHiHi.Condition” that sets the condition for the alarm. This allows the end user to refer to the alarm-related attributes in a consistent, intuitive way.
### Working with the Logger
Use the Logger only for tracing trapped software errors or diagnostics, and only use it sparingly in production objects. Do not use it to provide information that is intended for operators. Operators dont typically look at the Logger information, but rely on alarm and quality information instead.
If you use the Logger to trace diagnostic information, make sure that the logging does not continue indefinitely (for example, on every Application Engine scan). Otherwise, performance issues occur.
If you use the Logger to provide debugging information during development, either remove the logging calls before releasing the object to production, or change them so that logging only occurs when a custom log flag is set.
For more information on the Logger APIs, see the *ArchestrA* *Object Toolkit* *Reference Guide*.
### Raising Data Change Events
Wonderware Application Server supports generating Application Data Change events to report significant or unexpected data value changes to the alarm and event sub-system. To generate a Data Change event, use the SendEvent method of the objects run time component.
These events are intended for data changes that occur during the execute method of the object. They can be used to record data changes in event history. However, do not use them for data changes initiated by a run time user (“user sets”). This causes duplication, because these data changes are already logged by the ArchestrA infrastructure.
If you implement these events, you may want to provide a configuration option to enable or disable them. Users may not always want them reported, especially in the case of “noisy” data.
### Changing or Enforcing the Length of an Array
ArchestrA array lengths are dynamic. Run time or config time clients can change the length of an array by writing a new set of values to the array. The array length can also be changed at any time by the object itself. To enforce a fixed array length, check incoming values by using a set handler.
## Guidelines for Config Time Code Development
Use the following guidelines for developing good config time code.
### Ensuring Galaxy Dump/Load Support
Make sure that your object can be processed by the IDEs Galaxy Dump/Load feature without generating warnings. This features allows users of your object to dump object instances to a CSV file, modify their configuration, and then subsequently reload them. To ensure that this process works smoothly, you must follow certain rules:
- **Keep all validation rules in the config time code.** Do not rely on the custom editor code to maintain the integrity of the object (for example, keeping two attributes consistent with each other). The Galaxy Load feature does not use the editor code when importing objects. It only calls the config time codes OnValidate method. Therefore, any validation rules in the editor code are ignored during a Galaxy Load operation.
- **Set handlers must quietly accept a new value equal** **to the current value.** An object should not reject a set to an attribute when the value being set is the same as the previous value, even if the objects configuration does not currently allow that attribute to be changed. Coding this way prevents "noise" when Galaxy Load is run.
- **Avoid "write-only" attributes that modify the** **objects namespace.** An example of this is to have a set handler add, remove, or rename a primitive whose name was passed in as the value of a “write-only” attribute. At first, this appears to be a sensible way for an editor to pass a parameter to a config time method. However, if that information is not subsequently exposed as a readable attribute, there is not enough exposed information in a dumped CSV file to recreate the object from its configurable attributes when it is loaded.
Instead, you could store the names of the desired primitives in an attribute containing an array of strings. This array can have an associated set handler that maintains the number of primitives and their names. In this case, the Galaxy Load feature can load the object successfully, because the exposed array contains all the information required for the config time logic to recreate the primitives.
### Determining the Configuration Status
Every ArchestrA object has an associated configuration status: Good, Bad, or Warning. This status is based on the individual statuses reported by the primitives within the object. To set the status, use the OnValidate config time event.
The object status reported in the ArchestrA IDE is based on the worst status reported by any primitive within the object.
- Only set the status to **Bad** to prevent an object from being deployed. In general, you should design an object so that it can be deployed successfully with minimal configuration, and only set an objects status to Bad if deploying it in its current configuration would be impossible or dangerous.
- Use **Warning** status to mark an object as having a potentially incorrect, but still deployable configuration. For example, an object that still uses its default settings.
### Changing an Attribute's Data Type at Config Time
Sometimes you may need to change an attributes data type at configuration time. Normally, you will only do this for an attribute that you defined as a Variant (unspecified data type) in the Object Designer.
To change the attributes data type, modify the attributes data type property. For more information, see the *ArchestrA* *Object Toolkit* *Reference Guide*. For example, set the attributes data type property to a value of MxDouble to indicate that the attributes type is Double.
After changing the data type using the methods of the CMxVariant wrapper, the value is automatically initialized with the default value for that data type. If you change the data type using a Set call, you must initialize the new value manually.
## Guidelines for Run Time Code Development
Use the following guidelines for developing good run time code.
### Returning Warnings During Deployment
During deployment, objects can return a warning to the ArchestrA IDE user if the target environment is inconsistent with the objects configuration. The object continues to run despite the warning.
Returning warnings will rarely be necessary for ApplicationObjects, but if you want to do so, use the AddWarningMessage method. For more information, see the corresponding information in the *ArchestrA Object Toolkit* *Reference Guide*.
### Avoiding Application Engine "Overscans"
The Application Engine requires that runtime object method calls be nonblocking and relatively short in duration (on the order of 100 microseconds). You can create threads for slow or potentially blocking activities that would violate these requirements. However, make sure to terminate all threads when the object is shut down.
### OnScan/OffScan Behavior
You can define custom actions that are executed when your object goes OffScan. At a minimum, you should set the quality of any attributes that have the CalculatedQuality option enabled to Bad. When the object goes OnScan again, set the quality of these attributes back to Good.
### Dealing with Quality
Every attribute has an associated OPC-compliant data quality value that is a 16-bit word. The high-order byte is vendor-specific. In an ArchestrA environment, it is reserved for future use and currently always set to zero. The low-order byte specifies the OPC quality. It has three possible major quality states: Good, Uncertain, and Bad.
The ArchestrA environment additionally treats one substate of the OPC “Bad” state as the special quality of “Initializing.” Intializing quality is Bad quality with the Initializing bit set.
- If the quality of an attributes value is **Good**, the associated value can be trusted and used. However, the value could still be out of range or invalid (for example, NaN). Your object must check for these conditions separately.
- If the quality is **Uncertain**, the associated value can be used, but there is some doubt about the integrity of the value. For example, this could be the case when manually overriding an attribute that is normally calculated automatically. When using an input with Uncertain quality, do it with care and mark the resulting (calculated) attribute as Uncertain also.
- If the quality is **Bad**, there are a number of possible reasons. These include:
- The object that contains the attribute set its quality to Bad because insufficient or bad data was available.
- The infrastructure returns Bad quality for an attribute when the attribute cannot be accessed within Message Exchange. For example, the target attribute does not exist or communication is faulty.
- A field device may not be connected or accessible, resulting in Bad inputs that propagate through the system.
- **Initializing** quality is a form of Bad quality that requires special attention. It is temporary and only occurs while an object is initialized. It lasts until the object receives its first input data value. The quality then goes to Good, Bad (non-Initializing) or Uncertain.
Before you use data values in calculations and logic, always check their quality. For example, it does not make sense to calculate the average of two values if one or both values have Bad quality, since Bad quality indicates that the value is not to be used or trusted. Instead, in this case, you should skip the calculation of the average and set the resulting attribute to Bad quality itself.
The ArchestrA infrastructure does not automatically enforce a specific value (such as IEEE NaN) when quality is Bad, or a specific quality (such as Bad) when a value is NaN. Your object must check for these conditions before using any values in logic or calculations. For example, a float value read from a field device may have a value of NaN but Good quality. In that case, the object must be aware that the value may be unusable for a calculation. Conversely, a value read from a UDA attribute may be 4.3 but Bad quality. Again, the object must inspect the quality first, realize it is Bad, and take appropriate action.
#### Best Practices for Dealing with Quality
Best practices for dealing with quality include:
- If an attributes value is set by the objects run time logic, enable the **Supports Calculated Quality and Calculated** **Time** option for that attribute in the Object Designer.
- For static attributes (that is, attributes that you didnt create programmatically), you can use the auto-generated wrapper to access the attributes quality. For example:
```csharp
Attribute1.Quality = DataQuality.DataQualityGood;
```
- Set such attributes to Bad quality when the object goes OffScan. Set them to Initializing quality when the object goes OnScan.
- Do not use an input value with Bad (including Initializing) quality in a calculation. Instead, set the result quality Bad or Initializing (if input was Initializing) and leave the value at the last value. (For a float or double result, consider setting the result to NaN.)
- Do not use a NaN (float or double) input in a calculation. Instead, set the result to Bad quality and leave the result value at the last value, or set it to NaN if it is a float or double.
- If an illegal combination of input values exists, set the resulting quality to Bad.
- Optionally, provide an option to report a “bad value” alarm when a result value has Bad quality. Do not report a “bad value” alarm when a value has Initializing. Otherwise, transient alarms occur when the object goes OnScan.
- Do not trigger any other alarms when the quality of an attribute goes Bad. For example, do not trigger a PV change-of-state alarm when the PV goes to some default state after its quality goes Bad. Instead, always use a separate alarm for bad value reporting.
- Inputs with Uncertain quality can be used with care. Set the result to Uncertain quality also to indicate its questionable status.
- Do not generate Logger messages when setting an attribute to Bad quality in the cases outlined above.
- Do not attempt to change the quality of an input, output, or input/output by using its wrapper. This is not supported and may result in unexpected I/O values being written.
### Dealing with Timestamps
Observe the following guidelines when dealing with calculated attributes:
- In most cases, it is appropriate to enable the **Supports** **Calculated Quality and Calculated Time** option for values whose value is calculated at run time.
- For attributes that are updated based on the value of an input or input/output, set the time of the attribute to the input value's time. This ensures that timestamps are propagated properly.
- When setting the value of a calculated attribute that is not connected to an input, it is usually best practice to set the time to the current time. For attributes that have the **Supports Calculated Quality and Calculated Time** option enabled, the system automatically does this when you set the value.
- When setting the value of an attribute based on the value from another object, make sure to set the time of the attribute to the time from the CMxIndirect value. This ensures that timestamps are propagated properly.
### Dealing with Outputs on Object Startup
When developing objects associated with field devices, such as a PLC, there are two main scenarios for what happens when the object starts executing at run time:
- In the more common case, the object mirrors the PLCs data. In this scenario, when starting or resuming run time execution, the object must initialize its own state to match the PLC data. The object only writes data to the PLC when an operator, script etc. requests such output. It must *not* automatically write any data to the PLC when it is started or shut down, set OnScan/OffScan, deployed/undeployed, etc. This should be the default scenario.
- Rarely, the inverse may be necessary, and the PLC should mirror the objects data. In this scenario, when the object starts or resumes run time execution, it writes to the PLC to force the PLC to match the objects data. For example, when resuming execution after a failure, the object might use checkpoint data to restore the state before the failure. This scenario is much less common since the PLC generally is in control upon restarts.
In keeping with these scenarios, the utility primitives that do outputs (Output and InputOutput primitives) never do an output unless the object itself requests it. The object is in complete control of when outputs occur. Therefore, if you want to implement the second scenario, you must implement custom code that performs the outputs to initialize the field device.
> **Note**
> You can check the ESTARTUPCONTEXT input parameter to the Startup run time event handler to see why the object is starting up (deployment, etc.).
### Dealing with the Quarantine State
When an unhandled software error is detected in a primitive, the object is placed in a quarantine state indicating a bug in the primitive code. As a result, the primitives set handlers, Execute method, and other methods are no longer called. The only remaining calls that the primitive can receive are those related to the shutdown or undeployment of its associated object. However, you can still read the objects attributes to gather troubleshooting information about the object state at the time of the failure, because this doesnt involve calling any methods.
When an object is quarantined, the hosting engine raises an alarm that remains active until the object is undeployed.
### Ensuring Failover Support for Run Time Dynamic Attributes
Note the following guidelines for run time code when working with failover/checkpointing support for dynamic attributes:
- Attribute information may become outdated if the dynamic attribute is modified after it is created. To ensure that attributes are re-created correctly after a failover, call the UpdateDynamicAttributeData() method immediately after changing an attributes name, data type, category, security classification or set handler flag at run time. For more information, see the *ArchestrA Object Toolkit* *Reference Guide*.
- After you change the value of a dynamic attribute, call the CheckpointDynamicAttributeData() method either immediately or during the next scan cycle. This ensures that the attributes values are kept current in the failover environment. For more information, see the *ArchestrA Object Toolkit* *Reference Guide*.
- To restore dynamic attributes and their values at run time startup, call the RestoreDynamicAttributes() method from the objects Startup event handler. For more information, see the *ArchestrA Object Toolkit* *Reference Guide*. You can check the ESTARTUPCONTEXT input parameter to the Startup event handler to see why the object is starting up (deployment, failover, etc.).
## Guidelines for Custom Editor Development
Use the following guidelines for developing good code for your custom object editor.
### Keeping Validation Rules out of the Editor Code
Do not rely on the custom editor code to maintain the integrity of the object (for example, keeping two attributes consistent with each other). It should always be possible to create an object using a standalone configuration utility which configures the objects attributes without any involvement by the objects custom editor. Therefore, dont put validation rules in the custom object editor code. Instead, put them in the OnValidate config time event that is provided for this purpose.
### Creating a Complete Editor
Make sure that your custom object editor allows the user to edit every non-hidden configurable attribute of your object. Remember that you may even have to add non-configurable attributes to the editor, because their security classification might still be editable.