Asle Frantzen

Storing AF calculations to a PI tag

Blog Post created by Asle Frantzen Champion on Aug 26, 2012

A blog post Andreas made recently reminded me of this job I did for one of our clients some months ago. They had just started using AF, and we created some calculations for them using the cool interpolation feature of an AF table. After everything was set up we demonstrated the setup, and showed how to trend the calculations using ProcessBook.

 

And then one of the engineers raised her hand and asked: How can I see the values in Excel, using DataLink?

 

Knowing that I couldn't store the data from the AF calculation to a PI tag, and that DataLink wasn't AF enabled yet, I had to come up with a solution quickly. Performance Equations didn't have the strength of being able to interpolate values the way we could in AF, and I didn't really want to create the entire thing (again) in ACE.

 

But how about simply using ACE to read the value of the AF attribute, and store the value directly to a PI tag? That sounded like a quick solution, and by making it module dependent I could easily create a utility which could store multiple AF attributes to PI tags. It was sufficient to have it clock scheduled, which reduces the time to implement. (Naturally scheduled is also possible, but then you need to gather up all the PI tags from the PI Point data references in AF, from which the calculation is based upon, and put these as input tags to the ACE calculation).
 

 

Step 1

 

Let's start by defining the structure we need in the Module Database, like in the picture below:

  • Output: PIAlias pointing to the PI tag used for storing the output value
  • Logging: PIProperty, boolean value to determine whether or not logging to the event log is enabled
  • AF_Calculated_Attribute_Path: PIProperty, string value containing the full path to the attribute you wish to store. Example: "\\PISystem\AFDatabase\Element\Element\Element|Attribute"  

8055.vcampus_5F00_blog_5F00_august_5F00_2012_5F00_pic1.jpg

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

The PIModule under which these are created, will be the context to use when applying the calculation to new setups.

 

Step 2

 

Create a new ACE project, name it accordingly, select the output section and click the "Alias search" button to locate the "Output" tag alias we just created. The input section should remain empty (unless you want to attempt to create a naturally scheduled calculation, as mentioned).

 

Step 3

 

Make sure you have the necessary references added. Since we need to access AF attributes we'll need to have a reference to the AF SDK.

 

0675.vcampus_5F00_blog_5F00_august_5F00_2012_5F00_pic2.jpg

 

Step 4

 

Add the code. It's well documented, you can use it all or pick out the parts you need:

 
Imports OSIsoft.PI.ACE
Imports OSIsoft.PI.ACE.PIACEBIFunctions
Imports PITimeServer

Imports OSIsoft.AF
Imports OSIsoft.AF.Asset

Imports System.Collections.Generic

Public Class AFAttributeToPITagStorage
    Inherits PIACENetClassModule
    Private OutputTag As PIACEPoint
    Private Mdb_Module As PISDK.PIModule
    Private Shared log As EventLog
    Private Shared allPiSystems As PISystems
    Private Shared myPiSystem As PISystem
    Private Shared myAFDatabase As AFDatabase

    'Temporary calculation variables
    Dim piSystemName As String
    Dim afDatabaseName As String
    Dim myAttribute As AFAttribute
    Dim afAttributePaths As IEnumerable(Of String)
    Dim afErrors As IDictionary(Of String, String)


    'Properties
    Dim AF_Attribute_Path As String
    Dim Logging As Boolean


    '
    '      Tag Name/VB Variable Name Correspondence Table
    ' Tag Name                                VB Variable Name
    ' ------------------------------------------------------------
    ' OutputTag                               OutputTag
    '
    Public Overrides Sub ACECalculations()
        'Get PI System name from input path
        If (AF_Attribute_Path.StartsWith("\\")) Then
            piSystemName = AF_Attribute_Path.Substring(2).Split("\")(0)
        End If

        'Get AF Database name from input path
        If (AF_Attribute_Path.StartsWith("\\")) Then
            afDatabaseName = AF_Attribute_Path.Substring(2).Split("\")(1)
        End If

        'Add the path to the element to an IEnumerable list
        afAttributePaths = New List(Of String)(New String() {AF_Attribute_Path.Split("|")(0)})

        'Make sure PI System name is properly configured in the input path
        If (piSystemName Is "") Then
            If Logging Then
                log.WriteEntry("Error: PI System name is not properly configured in the MDB Property: " + DateTime.Now.ToString())
            End If

            'IMPORTANT to avoid CalcFailed in the archive
            OutputTag.SendDataToPI = False
        Else            
            myPiSystem = GetPISystem(piSystemName)

            If (Not myPiSystem.ConnectionInfo.IsConnected) Then
                myPiSystem.Connect()
            End If

            myAFDatabase = myPiSystem.Databases(afDatabaseName)

            If (IsDBNull(myAFDatabase)) Then
                If Logging Then
                    log.WriteEntry("Error: No PI System or AF Database found: " + DateTime.Now.ToString())
                End If
                OutputTag.SendDataToPI = False
            Else
                'Count the number of found elements. Should be only 1 since path is absolute!
                If (AFElement.FindElementsByPath(afAttributePaths, Nothing, afErrors).Count = 1) Then
                    myAttribute = AFElement.FindElementsByPath(afAttributePaths, Nothing, afErrors).First.Attributes(AF_Attribute_Path.Split("|")(1))

                    If (Not IsDBNull(myAttribute)) Then
                        If Logging Then
                            log.WriteEntry("Debug: Attribute " + AF_Attribute_Path.Split("|")(1) + " is " + myAttribute.GetValue().ToString() + " at: " + DateTime.Now.ToString())
                        End If

                        'Important: Cast to PIValue since AFValue gives "Wrong type" in the archive
                        OutputTag.Value = myAttribute.GetValue().ToPIValue()
                    Else

                        If Logging Then
                            log.WriteEntry("Error: Attribute " + AF_Attribute_Path.Split("|")(1) + " is null: " + DateTime.Now.ToString() + ". Error returned from AF: " + afErrors.First.ToString())
                        End If
                        OutputTag.SendDataToPI = False
                    End If
                Else
                    If Logging Then
                        log.WriteEntry("Error: More than one attribute by the name of " + AF_Attribute_Path.Split("|")(1) + " found: " + DateTime.Now.ToString())
                    End If
                    OutputTag.SendDataToPI = False
                End If
            End If
        End If
    End Sub

    Protected Overrides Sub InitializePIACEPoints()
        OutputTag = GetPIACEPoint("Output")
        Mdb_Module = GetPIModuleFromPath(Context)

        Init()
    End Sub

    Private Sub Init()
        OutputTag.ArcMode = PISDK.DataMergeConstants.dmReplaceDuplicates

        log = New EventLog()
        log.Log = "Application"
        log.Source = "PI-ACE"

        If Logging Then
            log.WriteEntry("Initializing calculation " + Mdb_Module.Name + ": " + DateTime.Now.ToString())
        End If
    End Sub

    '
    ' User-written module dependent initialization code
    '
    Protected Overrides Sub ModuleDependentInitialization()
        AF_Attribute_Path = Mdb_Module.PIProperties("AF_Calculated_Attribute_Path").Value
        Logging = Mdb_Module.PIProperties("Logging").Value
    End Sub

    '
    ' User-written module dependent termination code
    '
    Protected Overrides Sub ModuleDependentTermination()
        AF_Attribute_Path = String.Empty
        Logging = False
    End Sub

    '
    ' Return the PI System with the requested name, or the default one if no name is passed
    '
    Private Function GetPISystem(ByVal name) As PISystem
        allPiSystems = New PISystems()

        If (name Is "") Then
            GetPISystem = allPiSystems.DefaultPISystem
        Else
            GetPISystem = allPiSystems(name)
        End If

        If Logging Then
            log.WriteEntry("PISystem: " + GetPISystem.Name + ": " + DateTime.Now.ToString())
        End If

    End Function
End Class

 

 

Step 5

 

Test and register the ACE module. If you want to add new calculations you can deploy the calculation to new contexts, which should be set up in the Module Database in the same way as in step 1.

 

ACE will now read the current value from the AF attribute at the given clock scheduled times, and save the value to the output tag.

 

A small caveat in the Init() method, where you specify the log.Source. If you're in a Windows 2008 Server environment and the source doesn't exist, it won't be created automatically as it used to with previous versions. You'll have to explicitly create it (using PowerShell, google it) or possibly use another source which already exists.

 

 

 

So, while we're awaiting the arrival of the Configured Analytics, this is something you can do to store the values from AF attributes to PI tags.

Outcomes