Table of Contents
Before you begin writing a single line of code, you should first confirm that your needs absolutely cannot be met with an Asset Analytics. If a Custom Data Reference is absolutely justified, you should keep in mind that while you may have a solid understanding of the heart of your calculation (that is, what it suppose to do) that your calculation will be wrapped inside a Custom Data Reference. And that Custom Data Reference has certain features that must be addressed in order for your calculation to provide its results.
Your Custom Data Reference will need at a minimum:
- A Name and Description
- Its own GUID attribute
- To inherit from the AFDataReference class
In the example below, the name of the class is "BitMaskDR", but the name of the data reference is "Bit Mask". When assigning your data reference to an AFAttribute in PI System Explorer, the name that appears in the drop-down will be "Bit Mask".
Though not absolutely required, you most likely will need a ConfigString property. The ConfigString should be used to declare the various input attributes and options related to your calculation. Note the property's Setter parses a string into strong-typed pieces, and the Getter composes a string based upon those pieces. It's a common practice to separate those pieces with a semi-colon (";").
When you first start out, you may find it easy to use a ConfigString with fixed positions, as in "attribute1;attribute2". This may be great just for you, but as you may intend for your data reference to see a wider audience, you may find it friendlier to use 'name=value' patterns like "key1=value1;key2=value2;...". For example, which looks easier to understand and use:
If you use the first way, the positions are fixed and you've placed the burden on the end-user to know which position means which. For example, you really hope a new user doesn't place the Lo before Hi. But with the second way, you can write the Name=Value pairs in any order, or better yet the data reference would now have the flexibility to omit the High parameter, which is something you can't do with fixed positions.
Any request for data will first make a call to GetInputs to determine which attributes will be used as inputs for your calculation. GetInputs returns an AFAttributeList, so even if you only have one input attribute, you will need to wrap it in an AFAttributeList. If you have no input attributes, rather than return an empty AFAttributeList, the preferred convention is to return null in C# or Nothing in VB.NET.
Specifies which of the data reference methods are supported. Its value can be one or more of the AFDataReferenceMethod enumeration values logically ORed together.
At the very least, your data reference will probably support the GetValue method. If you have any historical or time-series data, you would also want to support the GetValues method.
If your data reference overrides the Step property, it is supported via the ZeroAndSpan flag. You don't necessarily have to override the Zero and Span properties to have your data reference support the ZeroAndSpan flag, but if you want the Step property exposed in PI Vision or ProcessBook, then you do have to declare that your data reference supports ZeroAndSpan.
SupportDataMethods Property Specifies which of the Rich Access Data (RDA) methods are supported by the data reference. Its value can be one or more of the AFDataMethods enumeration values logically ORed together. The default value is None.
Ultimately your calculation will return one value at a given time. This resulting value is exposed via the GetValue method, which returns an AFValue. When designing your data reference, keep in mind that others may refer to it as GetValue() with no time context provided, which usually denotes the current value is requested, or with GetValue(AFTime) , where an explicit timestamp was requested. There is also a GetValue(AFTimeRange) overload but do not be fooled because you have passed in a time range - you will still get only one AFValue returned no matter how large the time range.
Note it is up to you as the developer of the data reference to determine which timestamp is returned with a GetValue call. Here are some possibilities:
- GetValue() without a time context should first examine the timestamps of any of the input values. The convention would be to use the most recent timestamp among all of the inputs. An alternative would be to use AFTime.Now.
- GetValue(AFTime) by convention typically should use the passed AFTime.
- GetValue(AFTimeRange) should decide whether to use the EndTime versus the StartTime of the time range. Most data references use EndTime. A more flexible data reference would allow this to be declared as an option within the ConfigString.
You could place the heart of your calculation inside the GetValue method, but it may be a better design to have it as a separate method that can be called by GetValue. Why? Because your data reference may also support GetValues or RDA calls such as RecordedValue and RecordedValues, you may want them all to share the same call to your calculation.
It is possible that your data reference may not support GetValues. Usually, this is done if your input attributes are static or not time-series data. But if you do expect to output time-series data, then you may plan for your data reference to support GetValues.
You do not want to duplicate your calculation in GetValues (see the previous section). Nor do you want to call GetValue from GetValues, as this could possibly trigger another data fetch from the server. Instead, you would want to loop over the respective input data values and gather a single set of values for one event horizon. It may help to think of this as a row of data for a given timestamp. You would then call your calculation method for each row.
Coding Over the Video Series
The remainder of this course section will have a series of videos walking you through coding of a Custom Data Reference. The flow of the videos doesn't stop to reflect upon design choices, so as you watch the videos try to keep in mind the above discussions. During the video series, at a bare minimum you will have to:
- Create a new data reference project
- It must be assigned a Name and a GUID
- It must inherit from AFDataReference
- Create ConfigString property
- Create GetInputs method
- Created SupportedMethods property
- Create GetValue method
Plus there may be additional properties or methods depending upon further design choices.
Please use the discussion forum to discuss or ask any questions about this topic