Skip navigation
All Places > PI Developers Club > Blog > Author: Asle Frantzen

PI Developers Club

7 Posts authored by: Asle Frantzen Champion

Long time since since my last blog post, so I thought I'd share this little system integration trick I had to do for a customer in the T&D industry.


Different departments use ProcessBook for different content. The operation center is interested in fast flowing / high resolution data, such as PMU/synchro phasors, while the maintenance dept. is more interested in long term analysis for individual assets, or the whole fleet of assets. Some of the more intense PB display applications for the maintenance dept. take 20-30 seconds to load, as an example.


As a result we've ended up with setting up their ProcessBook installations with different data update frequencies. As you might already know, this is done by editing the Procbook.ini file in the PIPC\DAT folder - adding the following lines to the bottom of the file (number in milliseconds):

[Data Manager] Timer=10000

Now our customer has asked that we merge their different ProcessBook displays and AF databases into one package. Ideally we would like to set the refresh rate for each display independently, but unfortunately that isn't possible. Instead I created two shortcuts to start ProcessBook with different update frequencies, and basically change between two Procbook.ini files containing different settings every time the program is started. Here's what I did:

  1. I didn't want to work directly in the PIPC\DAT folder, as I believe that'll require elevated permissions every time you "start" ProcessBook. Instead I looked at the current user's AppData folder, where I know a copy of Procbook.ini is placed the first time a user starts ProcessBook after installation. Here I made two copies of Procbook.ini with different names, and different configurations. For this example I use 10 and 20 seconds as update frequencies. I start with "Procbook.ini" and "Procbook_10sec.ini".
  2. The next step was to create a PowerShell script to do the swap between the two files, and then start ProcessBook.exe. Here's the script, which takes a parameter to determine if you want 10 or 20 seconds:
    param([Int32]$Seconds=10) $pbIni = $env:APPDATA + '\PISystem\PI-ProcessBook\en\ProcBook.ini' $pb20sec = $env:APPDATA + '\PISystem\PI-ProcessBook\en\ProcBook_20sec.ini' $pb10sec = $env:APPDATA + '\PISystem\PI-ProcessBook\en\ProcBook_10sec.ini'  If ($Seconds -eq 10)  {      Rename-Item $pbIni ProcBook_20sec.ini      Rename-Item $pb10sec ProcBook.ini } Else {      Rename-Item $pbIni ProcBook_10sec.ini      Rename-Item $pb20sec ProcBook.ini }  $path = $env:PIHOME + '\Procbook\Procbook.exe' & $path

    The script loads with the parameter "Seconds" = 10 as default.

  3. If you havent run PowerShell scripts in your computer before, you need to set the execution policy to be allowed to execute them. Run this in an elevated PowerShell prompt (Scope = LocalMachine means all users, or alternatively CurrentUser if you only want your own account to be allowed):
    Set-ExecutionPolicy RemoteSigned -Scope LocalMachine

  4. Next step is to create two shortcuts on your desktop. Since PowerShell scripts cannot be executed by double clicking them we need to work around it. The paths for the two shortcuts should be:
    C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command "& 'C:\Path\To\Your\Script\script.ps1' -Seconds 10"
    C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command "& 'C:\Path\To\Your\Script\script.ps1' -Seconds 20"

  5. The last step is just for looks, but make sure you configure the shortcuts to have the same icon as ProcessBook.exe. Go into the properties of the shortcut, click "Change icon" and browse your way into the PIPC\Procbook folder to select ProcessBook.exe.




Now you'll have two shortcuts on your desktop where you can quickly select between the two different data update frequencies. Might not be necessary for most users, but there are always someone who might find it useful!

For some time I've requested a better regression line visualization in ProcessBook, on behalf of one of our clients. While delivering some displays to them as part of a condition monitoring system, they needed to see the regression lines on some trends with very little amplitude. We enabled the standard regression line in the ProcessBook trend definition window, which outputs a dashed line in the same pen-color as the data point it belongs to. Since the amplitude of the trend was so low, and they needed to have a long default timerange, you could barely see it moving up and down. And when enabling the standard regression line on top of that, with the same color and a dashed line, they meant it was too confusing and disabled the whole thing.


After that I downloaded the old DevNet Regression line addin from the Library, to see if this suited them better. And while the visualization part of that was perfect for them, the technology turned out to be outdated. It only supported PI tags, and all of the displays for the CMS project were ERD based. It used the PI API, and didn't appear to support trusted connections to the PI server. And today "nobody" uses explicit logins (often it's even disabled), and "everybody" uses AD mappings and trusted connections.


Eventually I decided to take matters into my own hands, and transfer the Regression line addin into the 21st century (yes, the old one was from the 20th century). I got the code for old addin from Andreas in the vCampus team, and being that it used the PI API in Visual Basic 6 I didn't even try to get this up and running in any way before starting.


Using the Visual Studio templates for creating ProcessBook AddIns, I got a simple development platform up and running. I also followed a LearningLab guide I had on a USB stick after one of the first vCampus Live! events, called ".NET add-in to Implement a Custom Dataset". This was very useful since this was my first dataset addin. Previously I had only created docking window addins, and "ActiveX" addins - which can be used in Microsoft Office products as well.


As with most development projects I probably spent 20% of the time reaching 80% in progress. After getting the GUI as I wanted, implemented the IDataProvider3 interface from the PBObjLib assembly, and created the math to do the actual calculation, a lot of debugging was in place. Sometimes the line was drawn perfectly, other times there was only one value plotted to the trend. But eventually I got it all working. The options other than the regression line itself are the "Slope" and "Correlation coefficient". (The slope is defined for the last 24 hours).


I've used the standard OSIsoft UI elements as much as possible. The ERD dialog in ProcessBook seemed to be custom made, so I had to recreate that. I even simplified it a little bit. In the ProcessBook ERD feature you can open the ERD attribute selection dialog even if you don't have any elements of interest defined. Then you first have to define it, before you can select the attribute you're interested in. For simplicity I've not implemented this functionality, and in my addin you will have to define some EOI's before you open the RegressionLine addin dialogs or else the controls will be disabled. I do however plan on setting this up as a community project, so anyone wanting this (or other) functionality can easily implement it later.


Ok, enough writing - let's see some screenshots and then download it  


(Click images to see larger versions!)






After installing the addin and opening ProcessBook you'll find another data source in the Define Trend window. Click "RegLine" to open the dialog.






















In the RegressionLine AddIn window we can start by browsing for PI tags, and as you can see you're in a familiar environment since the standard OSIsoft UI elements are used.


























After selecting a tag to see the regression line of (in this case CDT158), and also adding that tag to the trend, we get the following result:
















2502.thumb_5F00_step4.jpgThe following screenshot shows the AF tab of the addin, and the rest should be pretty self-explanatory:






















As mentioned, I had to recreate the ERD Selected attribute dialog since that was not part of the UI library provided by OSIsoft. I simplified it a bit, so it's enough to either doubleclick on the attribute of your choice - or click it once before hitting the OK button.


































5314.thumb_5F00_step6.jpgIf everything is done right, you should then be able to use this addin for ERD based displays:
















So that's it. You can download the addin yourself, just unzip and run Setup.exe:

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.

I thought I'd share this trick we've used, when we want to utilize the functionality of PI Webparts but our client uses another product / technology for hosting their intranet pages.


As you may already know, Sharepoint IS required for hosting the PI Webparts, so we need it to be present in some form for this to work. Companies may already have Sharepoint server without them using it, and the free versions work fine for our task. Sharepoint Foundation 2010 is the latest version, and Windows Sharepoint Services (WSS3) is the previous one.


Our goal is to use the webparts in webpages hosted outside Sharepoint - so how do we do that? Simple - just set it up using an iframe!




Since Sharepoint displays a lot of navigation menus, headers, buttons, etc. in addition to the content we're looking for, we need to find a way of getting rid of this. Sharepoint uses master pages to set up many of these items which are called 'placeholders', so we'll have to modify the master page to make them go away. Because of the way Sharepoint works, these placeholders still need to be present in the master page, so we'll have to go through all of them and either clear the contents or set the Visible property to false.




Step 1


Copy one of the existing master pages found in the master page gallery of your Sharepoint server, preferably the default.master. You can use the freely available Sharepoint Designer, or just Notepad. Rename it (I've called mine zen.master) and delete the content of most of the placeholders - or set Visible="false".






Step 2


After finishing the master page, you need to create a webpart page which should use your newly created master page. This webpart page should also contain the PI Webpart you wish to expose through the iframe, but be sure to change the master page before adding the PI Webpart, as they don't normally like to function after being edited in Sharepoint Designer. Only a few placeholders should be created here, most important one is the "PlaceHolderMain" which will contain the WebPartZone where webparts are allowed to be dropped onto.






Step 3


Create your webpage outside of the Sharepoint environment, and include the iframe tag to get the contents from the Sharepoint webpart page. Example:

<iframe src="http://server/page.aspx" frameborder="no" width="621px" height="421px" scrolling="no"> </iframe>



A normal Sharepoint page with an RtTrend webpart could look something like this:




And after applying the zen master page we are left with this:

3288.RtTrend_5F00_admin_5F00_user_5F00_small.jpg 5875.RtTrend_5F00_normal_5F00_user_5F00_small.jpg
Webpage, admin user Webpage, non-admin user



As you can see, the "Site Actions" button is kept on the page, but it is only visible for users with enough permissions to edit the Sharepoint pages. So for normal users this would not be visible.


Please note that I've only tested this with MOSS / WSS3, as you also can see from the screenshots, but everything should work properly in Sharepoint 2010 as well. (Though you may have to do a couple of extra steps to get rid of the Office-style ribbons introduced in Sharepoint 2010)


The attached file contains a copy of my master page, a Sharepoint aspx page, and also a normal non-Sharepoint aspx webpage.

In my first blog post I presented updated performance results for the case I presented at vCampus Live! 2010. One of the things I briefly talk about in my presentation, is that I use an XSD Tool to create C# classes from an XSD file which comes along with AF. Today I would like to go through this process step by step.


The file OSIsoft.AF.xsd is found in the AF folder, which is located in the PIPC folder.


The tool is called XSD.exe and is found in the folder C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin - at least on my computer. It's very easy to use, and there are lots of options you can turn on. The default language is C# so we don't have to specify that, but you can use and other languages as well. Type xsd.exe /? to get a complete list of options.


The command I've used is:


xsd.exe OSIsoft.AF.xsd /Classes /namespace:Amitec.AF_Import


and this will generate a .cs file which you can import into your Visual Studio solution. The /Classes flag must be present, and I've also specified the namespace of my C# project.




I've imported both the .xsd and the .cs file into my Visual Studio solution, here are a couple of screenshots:

7875.vs_5F00_OSIsoft_5F00_AF_5F00_xsd_5F00_small.jpg 5756.vs_5F00_OSIsoft_5F00_AF_5F00_cs_5F00_small.jpg
OSIsoft.AF.xsd OSIsoft_AF.cs



Now we can start using the classes in the .cs file. This will be an "AF disconnected way" of dealing with a data hierarchy - as it doesn't connect to AF in any way, as opposed to using the AF SDK. You could start with something like this:

AF m_AFInstance = new AF();        
AFElement myAFElement = new AFElement();
myAFElement.Name = "FirstElement";
myAFElement.Description = "My first AF Element";

m_AFInstance.Items[0] = myAFElement;



After completing the business logic for your application you can do like I did, serialize your AF instance to XML - so that you can import it through PI System Explorer:

System.Xml.Serialization.XmlSerializer xSerializer = new System.Xml.Serialization.XmlSerializer(typeof(AF));
TextWriter writer = new StreamWriter("AF_Import_"+DateTime.Now.ToString("ddMMyyyy_HHmmsss")+".xml");
xSerializer.Serialize(writer, m_AFInstance);


Asle Frantzen

15 years in realtime

Posted by Asle Frantzen Champion Apr 1, 2011

I'd like to take the opportunity to congratulate Amitec with the 15th anniversary today, 1st of April, with a picture of the great tasting cake we had during lunch



Wow! My first blog post as an OSIsoft vCampus All-Star. Only six months after the vCampus Live! event of 2010!


I've been planning on writing this blog post ever since I got home from San Francisco last september, been extremely busy with interesting projects during the winter, and finally found some time for it today.












At vCampus Live! 2010 I presented the PI Asset Framework (AF) Speeds Up Time to Solution together with Stephen Kwan. If you didn't attend, you can watch it or download the original presentation at this link. The presentation shows my different efforts on finding the best way to import a hierarchical data structure from a lifecycle asset information management system called COMOS, and I tested the performance of these three options:



  1. Importing the data using the AFBuilder plugin for Excel
  2. Using the AFImport command line tool, (the same as importing through PI System Explorer)
  3. Using the AF SDK through Microsoft Visual Studio

I ended up using the second option - to import the data through the AFImport command line tool, as this turned out to be the most efficient way for me. As you can see in the presentation - or in the image to the right - I ended up with an average performance of appr. 500-700 elements per minute. My most efficient run included 133 430 elements based on a single Element Template, imported in 2 hours and 55 minutes - averaging at 762 elements per minute.


After my presentation I spoke with Chris Manhard, AF Development Lead at OSIsoft. He thought my performance seemed a bit lower than expected, and after I had returned home to Norway we did some investigations around my setup to see what would cause this. The result was I didn't have AF Server 2010 installed, and he explained they had made significant performance improvements with this release compared to the previous. I had been working on this project since early 2010, and the 2010 line of products - including the AF Server 2010 - wasn't released before August, about a month before the vCampus Live! event. So my results were all based on the AF version released in December 2009.






So, after installing AF Server 2010, here are the updated numbers:

  • 133 430 elements based on a single Element Template
  • Imported in 8 minutes and 45 seconds
  • Average speed of 15 250 elements per minute.

Now, that - ladies and gentlemen - is a 95% reduction in time!
Which truly means that "PI AF Speeds up time to solution"




I have added a new slide to the presentation, with the updated findings. Click here to download it!

Filter Blog

By date: By tag: