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"  





















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.




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.