You are on page 1of 2

GetValue method

There's several related methods here because we want to account for missing attributes and bad data.

Because I try to avoid magic numbers, and also have the code be self-documenting, I am creating a
special class containing a few constants.

private class TripState


{
public const int None = 0;
public const int LowLow = -2;
public const int Low = -1;
public const int High = 1;
public const int HighHigh = 2;
}

The GetValue implementation's main concern will be assigning the inputAttributes and associated
inputValues into specific values for a given limit trait or measurement. This logic allows for certain values
to be null. Then we call another a method named Calculation that is looking for specifically named values
in a specific order. You will note that the timeContext is converted to an AFTime.

// https://techsupport.osisoft.com/Documentation/PI-AF-
SDK/html/M_OSIsoft_AF_Asset_AFDataReference_GetValue_1.htm
public override AFValue GetValue(object context, object timeContext, AFAttributeList inputAttributes,
AFValues inputValues)
{
// Important to note that the order of inputValues matches the order of inputAttributes.

// Note that timeContext is an object.


// We need to examine it further in order to resolve it to an AFTime.
var time = ToAFTime(timeContext);

AFValue measurement = null;


AFValue low = null;
AFValue high = null;
AFValue lowlow = null;
AFValue highhigh = null;

// https://techsupport.osisoft.com/Documentation/PI-AF-
SDK/html/P_OSIsoft_AF_Asset_AFAttribute_Trait.htm
// https://techsupport.osisoft.com/Documentation/PI-AF-
SDK/html/T_OSIsoft_AF_Asset_AFAttributeTrait.htm
for (var i = 0; i < inputAttributes.Count; i++)
{
if (i == 0)
{
measurement = inputValues[i];
}
else if (inputAttributes[i].Trait == AFAttributeTrait.LimitLo)
{
low = inputValues[i];
}
else if (inputAttributes[i].Trait == AFAttributeTrait.LimitHi)
{
high = inputValues[i];
}
else if (inputAttributes[i].Trait == AFAttributeTrait.LimitLoLo)
{
lowlow = inputValues[i];
}
else if (inputAttributes[i].Trait == AFAttributeTrait.LimitHiHi)
{
highhigh = inputValues[i];
}
}
// Remember any of the passed AFValues could be null if the limit trait is not defined.
// This is a fact of life and reflects the many possibilities within a given process unit.
return Calculation(time, measurement
, low
, high
, lowlow
, highhigh);
}

This Calculation overload takes named AFValues. Any bad data, be it null AFValue or a bad state, will be
converted to double.NaN.

private AFValue Calculation(AFTime time, AFValue measurement, AFValue low, AFValue high, AFValue lowlow,
AFValue highhigh)
{
// Our custom ToDouble returns double.NaN for null, missing, bad data, or bad conversions.
var numericMeasurement = ToDouble(measurement);
if (double.IsNaN(numericMeasurement))
{
// https://techsupport.osisoft.com/Documentation/PI-AF-
SDK/html/M_OSIsoft_AF_Asset_AFValue_CreateSystemStateValue.htm
return AFValue.CreateSystemStateValue(Attribute, AFSystemStateCode.NoResult, time);
}
// Remember: any or all of the limits could be null. In those bad cases, a double.NaN is sent.
var calc = Calculation(numericMeasurement, ToDouble(low), ToDouble(high), ToDouble(lowlow),
ToDouble(highhigh));
return new AFValue(Attribute, calc, time);
}

Now that we've taken care of missing attributes and AFValue(s), and also taken care of bad data by
forcing them all consistently to double.NaN, we can run them through the final Calculation engine.

private static int Calculation(double measurement, double low, double high, double lowlow, double
highhigh)
{
// If any limits were missing or bad, they will be a NaN here.
// A NaN used in comparisons will return false, which is what we want below. That is,
// a comparison will return true only if the limit is not NaN and the limit has been met.
if (measurement >= highhigh)
{
return TripState.HighHigh;
}
if (measurement <= lowlow)
{
return TripState.LowLow;
}
if (measurement >= high)
{
return TripState.High;
}
if (measurement <= low)
{
return TripState.Low;
}
return TripState.None;
}

You might also like