Skip navigation
All Places > PI Developers Club > Blog > Authors Marcos Vainer Loeff
1 2 3 Previous Next

PI Developers Club

78 Posts authored by: Marcos Vainer Loeff Employee



After almost 4 years, it is time to rewrite/update my old ASP.NET MVC 5 blog post since the recommended Microsoft technology nowadays for web development is ASP.NET Core MVC 2.


But what is ASP.NET Core MVC?


According to this web page, the ASP.NET Core MVC framework is a lightweight, open source, highly testable presentation framework optimized for use with ASP.NET Core.

ASP.NET Core MVC provides a patterns-based way to build dynamic websites that enables a clean separation of concerns. It gives you full control over markup, supports TDD-friendly development and uses the latest web standards.


Can ASP.NET Core MVC be used together with PI AF SDK?


Since PI AF SDK is compiled against .NET Framework, one could argue that PI AF SDK is not compatible with ASP.NET Core since it was not compiled against .NET Core. Nevertheless, you can create an ASP.NET Core MVC project against .NET Core or .NET Framework. If you choose the second option, you can add PI AF SDK as a reference to your project.




If you want to follow this blog post along, please make sure the following software are installed on your computer:

  • Visual Studio 2017 Update 3
  • PI AF SDK 2017 R2+



Source code package

You can have access to the source code package related to this blog post in this GitHub repository.


Creating the project


Let's see how this works. Open Visual Studio and create a new project from scratch. Select the Web item on the left pane and then "ASP.NET Core Web Application". Then write "ASPNETCoreWithAFSDKPar1"as the name of the project and click on the "Ok" button.



Figure 1 – Creating a new  ASP.NET Core MVC project in Visual Studio.



On the next window, make sure that ".NET Framework" and "ASP.NET Core 2.0" are selected. Next, select the Empty template and then click on “OK”. Figure 2 shows the screenshot for selecting the template. We have chosen the empty template instead of the MVC template because the last option not only comes with the MVC binaries but also with some Controllers, Views, Layouts and the ASP.NET Identity responsible for the authentication and authorization from your web site. As we are not going to use many of those default files, the Empty seemed to be a better option for me.





Figure 2 – Selecting the ASP.NET Core template.


If you don't see those options on this window, make sure you have an updated version of Visual Studio 2017.


Right click on Dependencies and then "Add Reference..". Browse to the PI AF SDK binary and add it to your project.


Figure 3 – Adding PI AF SDK to your project


Before we start coding, make sure the target framework is .NET Framework and not .NET Core. Right click on the project and select properties to verify this information.


Figure 4 – VS project properties


Add NuGet libraries


Open the ASPNETCoreWithAFSDKPart1.csproj file and update it with the following content.


<Project Sdk="Microsoft.NET.Sdk.Web">


    <Content Remove="bower.json" />

    <Folder Include="wwwroot\" />

    <PackageReference Include="Microsoft.AspNetCore" Version="2.0.3" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.3" />
    <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.3" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.2" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.0.2" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.2" />
    <PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="2.0.2" />
    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.3" />

    <Reference Include="OSIsoft.AFSDK">
      <HintPath>..\..\..\..\..\Program Files (x86)\PIPC\AF\PublicAssemblies\4.0\OSIsoft.AFSDK.dll</HintPath>

    <Content Update="Views\Shared\_Layout.cshtml">



Save and recompile this project. The missing libraries will be retrieved from the internet and will be added to your project automatically.


Configure the HTTP requests pipeline


Open the Startup.cs file and edit it according to the content below:


 public class Startup
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit
        public void ConfigureServices(IServiceCollection services)

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)


This will make sure that:

  • MVC is enabled.
  • Static files are enabled.
  • Developer exception pages are shown if an exception is thrown (Don't use it on production).


Setting up Bower and adding Bootstrap


We are going to use bootstrap 3 to style our web site. Although there are better options nowaways, we are going to use bower to download bootstrap and jQuery. On the VS project root create two file: .bowerrc and bower.json.



  "directory": "wwwroot/lib"




  "name": "ASPNETCOreWithAFSDKPart1",
  "private": true,
  "dependencies": {
    "bootstrap": "3.3.7"


The first file tells the system to paste the retrieved libraries on the wwwroot//lib folder. The second file has all the dependencies.


Open the command prompt and change directory to the VS project folder. Then run"bower install" to install Bootstrap 3.3.7 and jQuery (both are JavaScript libraries), according to the dependencies listed on the bower.json file.


Creating the Home Controller


Let's create a new folder called Controllers and our home controller (new file homeController.cs) with folowing code:


using System.Collections.Generic;
using System.Linq;
using ASPNETCoreWithAFSDKPart1.Models;
using Microsoft.AspNetCore.Mvc;
using OSIsoft.AF.PI;

namespace ASPNETCoreWithAFSDKPart1.Controllers
    public class HomeController : Controller
        public IActionResult Index()
            return this.RedirectToAction("Query");

        public IActionResult Query()
            return View();

        public IActionResult Result(string pointNameQuery, string pointSourceQuery)
            PIServer piServer = new PIServers()["MARC-PI2016"];

            PIPointQuery query1 = new PIPointQuery(PICommonPointAttributes.Tag, OSIsoft.AF.Search.AFSearchOperator.Equal, pointNameQuery);
            PIPointQuery query2 = new PIPointQuery(PICommonPointAttributes.PointSource, OSIsoft.AF.Search.AFSearchOperator.Equal, pointSourceQuery);
            IEnumerable<PIPoint> foundPoints = PIPoint.FindPIPoints(piServer, new PIPointQuery[] { query1, query2 });
            PIPointList pointList = new PIPointList(foundPoints.Take(1000));

            List<PIPointSnapshotModelView> pointSnapshotValueList = new List<PIPointSnapshotModelView>();
            foreach (PIPoint point in pointList)
                pointSnapshotValueList.Add(new PIPointSnapshotModelView(point.Name.ToString(), point.CurrentValue()));

            return View(pointSnapshotValueList.AsEnumerable());


When the web site starts, it will load the Index() action, which will be redirected to the Query() action. This action will display a page with a form. When the user submits the form, the Result action will be called. PI AF SDK will search for PI Points matching certain conditions with the help of PIPointQuery class. The results are converted to an IEnumerable<PIPointSnapshotModelView>.



Creating the Model


The model all has the information needed to display to the use (PI Point name, value and attribute). Please create a new file PIPointSnapshotModelView.cs in the Models folder. Create the Models folder if it doesn't exist.


using OSIsoft.AF.Asset;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ASPNETCoreWithAFSDKPart1.Models
    public class PIPointSnapshotModelView
        public string PointName { get; set; }
        public object Value { get; set; }
        public DateTime TimeStamp { get; set; }

        public PIPointSnapshotModelView(string pointName, AFValue afValue)
            PointName = pointName;
            Value = afValue.Value;
            TimeStamp = afValue.Timestamp;



Creating the Shared View


Please create a new folder on the root called Views. Then create 3 files with the content below:



    Layout = "_Layout";




@using  ASPNETCoreWithAFSDKPart1.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers


Shared\_Layout.cshtml (don't forget to create the Views\Shared folder first!)

@model IEnumerable<ASPNETCoreWithAFSDKPart1.Models.PIPointSnapshotModelView>
    Layout = null;

<!DOCTYPE html>

    <meta name="viewport" content="width=device-width" />
    <link href="~/lib/tether/dist/css/tether.css" rel="stylesheet" />
    <link href="~/lib/bootstrap/dist/css/bootstrap.css" rel="stylesheet" />
    <script src="~/lib/tether/dist/js/tether.js"></script>
    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>



  • _ViewStart file can be used to define common view code that you want to execute at the start of each View's rendering.
  • _ViewImports.cshtml serves one major purpose: to provide namespaces which can be used by all other views.
  • _Layout: is the master layout that will be used by Query.cshtml and Result.html.


Creating the Views


Now, we just need to create Query.cshtml and Result.cshtml to finish our application!



<div class="col-sm-8 col-sm-offset-2">
    <h1>My first ASP.NET Core MVC with PI AF SDK</h1>
    <br /><br />
    <h4>Search for PI Points and check its snapshots.</h4>
    <br /><br />
    <div class="container">
        <div class="row">
            <form asp-controller="Home" asp-action="Result" method="get" class="form-inline">
                <div class="form-group">
                    <label class="sr-only" for="name">Search PI Point: </label>
                    <input id="pointNameQuery" name="pointNameQuery"  placeholder="Point Name" class="form-control" />
                <div class="form-group">
                    <label class="sr-only" for="inputfile">With this Point Source: </label>
                    <input id="pointSourceQuery" name="pointSourceQuery" placeholder="Point Source" class="form-control" />
                <button type="submit" class="btn btn-default">Submit</button>





@model IEnumerable<PIPointSnapshotModelView>

<div class="col-sm-8 col-sm-offset-2">
    <h1>My first ASP.NET Core MVC with PI AF SDK</h1>
    <br /><br />
    <table class="table">
            <th>PI Point</th>
        @foreach (var pointSnapshotValue in Model)
    <br />
        <a asp-controller="Home" asp-action="Index" class="btn btn-primary">Make another search</a>



Testing the web app


We have finished writing our ASP.NET Core MVC web application. It is time to test. When you run this web application on Visual Studio, you should see the Query page below.



Figure 5 – Screenshot of the Query page from our web application


Figure 6 shows a screenshot in case we want to find all PI Points which match the “sin*d” from any PI Point Source. After clicking on submit, it will show the table with the PI Points found on the Result page.



Figure 6 – Screenshot of the Result page from our web application


Note that the design of the page has improved using bootstrap adding only a few words to the View. The query string defines the input parameters so you can edit them directly on the uri. If you click on “Make another search” it should go back to the previous page.



If you are a PI Developer that needs to develop a web application on top of the PI System., ASP.NET Core MVC with PI AF SDK is a great option. Nevertheless, there are other options as well, which we are going to see on my upcoming blog posts. Stay tuned!

I am happy to announce that the client libraries below were updated using the new PI Web API 2018 Swagger specification. All the new methods from PI Web API 2018 are available for you to use on the client side! The StreamUpdate related methods (still in CTP) were also added to those projects.



If you have questions, please don't hesitate to ask!



PI Web API 2018 was released in late June with some interesting features. In this blog post, I will show you some of those features using the PI Web API client library for .NET Standard which was already upgraded to 2018.


According to the Changelog of the PI Web API 2018 help, the new features of PI Web API 2018 are listed below:


  • Read for notification contact templates, notification rules, notification rule subscribers and notification rule templates
  • Read, create, update and delete for annotation attachments on event frames
  • Retrieve relative paths of an element
  • Retrieve full inheritance branch of an element template
  • Allow reading annotations of a stream using the "associations" parameter
  • Allow showing all child attribute templates of an element template
  • Add parameter support for tables
  • Filter attributes by trait and trait category
  • Support health traits
  • Incrementally receive updates from streams with Stream Updates (CTP)

Data model changes:

  • Expose 'ServerTime' property on objects of asset server, data server and system
  • Expose 'DisplayDigits', 'Span' and 'Zero' properties on objects of attribute and point
  • Expose 'DefaultUnitsNameAbbreviation' property on objects of attribute and attribute template


You can have an idea of the new features by looking at the list above but showing some code snippets will help you understand better.


Please visit the GitHub repository to download the source code package used in this blog post.




Before we start, please create a .NET Framework or .NET Core console application and add the OSIsoft.PIDevClub.PIWebApiClient according to the README.MD file of the client library repository if you want to try yourself.



Read for notification contact templates, notification rules, notification rule subscribers and notification rule templates


PI Web API 2018 comes with 4 new controllers (NotificationContactTemplate, NotificationRule, NotificationRuleSubscriber and NotificationRuleTemplate) with read access to the notification objects.


For this demonstration, I have created a Notification Rule to make sure that it is accessible through client.NotificationRule.GetNotificationRuleQuery() action.



            PIAssetDatabase db = client.AssetDatabase.GetByPath(@"\\MARC-PI2016\WhatsNewInPIWebAPI2018");
            PIItemsNotificationRule notificationRules = client.NotificationRule.GetNotificationRulesQuery(db.WebId, query: "Name:=Not*");
            Console.WriteLine($"Found {notificationRules.Items.Count}");


HTTP Request: GET - /piwebapi/notificationrules/search?databaseWebId=F1RDbvBs-758HkKnOzuxolJRlgBW-drMj-Q0eZlG9e0V3JigTUFSQy1QSTIwMTZcV0hBVFNORVdJTlBJV0VCQVBJMjAxOA&query=Name%3a%3dNot*


Running this application, the result is:



Read, create, update and delete for annotation attachments on event frames


I have manually created a new Event Frame (EF) using PI System Explorer. I want to retrieve the same EF programmatically and add an annotation with a value of "EF Test" through the CreateAnnotiationWithHttpInfo() method available of the client library. I've reviewed the status code to make sure that the request was made successfully.



            PIAssetDatabase db = client.AssetDatabase.GetByPath(@"\\MARC-PI2016\WhatsNewInPIWebAPI2018");
            PIItemsEventFrame efs = client.AssetDatabase.GetEventFrames(db.WebId);
            PIEventFrame ef = efs.Items.First();
            PIAnnotation piAnnotation = new PIAnnotation();
            piAnnotation.Value = "EF Test";
            ApiResponse<object> result = client.EventFrame.CreateAnnotationWithHttpInfo(ef.WebId, piAnnotation);
            if (result.StatusCode < 300)
                Console.WriteLine("Annotation in EF was created successfully!");

            PIItemsAnnotation piAnnotations = client.EventFrame.GetAnnotations(ef.WebId);
            foreach (PIAnnotation annotation in piAnnotations.Items)
                Console.WriteLine($"Annotation from Event Frame: {annotation.Value}");


HTTP Request: POST - /piwebapi/eventframes/F1FmbvBs-758HkKnOzuxolJRlgbFJEBGSE6BGbywAVXQAeEATUFSQy1QSTIwMTZcV0hBVFNORVdJTlBJV0VCQVBJMjAxOFxFVkVOVEZSQU1FU1tFRjIwMTgwNzEwLTAwMV0/annotations


I can see my created annotations in PI System Explorer. Just right click on the EF and then click on "Annotate..." You will be able to see all EFs generated programmatically.



Retrieve relative paths of an element


PI Web API 2018 allows you to get a list of the full or relative paths to this display. Let's see how this works. Please refer to the AF Tree below.




Using the code snippet below.


            PIElement element = client.Element.GetByPath(@"\\MARC-PI2016\AFPartnerCourseWeather\Cities\Chicago");
            PIItemsstring relativePath = client.Element.GetPaths(element.WebId, @"\\MARC-PI2016\AFPartnerCourseWeather");
            Console.WriteLine($"The Relative Path is {relativePath.Items.First()}.");


The second line calls GetPaths with the second input being "\\MARC-PI2016\AFPartnerCourseWeather" as the full path of one of their parents' AF object, which in this case is the AF database.


HTTP Request: GET -  /piwebapi/elements/F1EmbvBs-758HkKnOzuxolJRlg-Jnwgsge5hGAwwAVXX0eAQTUFSQy1QSTIwMTZcQUZQQVJUTkVSQ09VUlNFV0VBVEhFUlxDSVRJRVNcQ0hJQ0FHTw/paths?relativePath=%5c%5cMARC-PI2016%5cAFPartnerCourseWeather


The result will be the element relative path which is the path relative to the element path of one of its parents.



Retrieve full inheritance branch of an element template


In this example I have create 3 element templates and 1 element:

  • TemplateA
  • TemplateAA derived from TemplateA
  • TemplateAAA derived from TemplateAA.



PI Web API provides two interesting methods: GetBaseElementTemplates() and GetDerivedElementTemplates() to get the base and derived element templates from element templates. Let's see how this work:


            PIElementTemplate templateA = client.ElementTemplate.GetByPath("\\\\MARC-PI2016\\WhatsNewInPIWebAPI2018\\ElementTemplates[TemplateA]");
            PIElementTemplate templateAA = client.ElementTemplate.GetByPath("\\\\MARC-PI2016\\WhatsNewInPIWebAPI2018\\ElementTemplates[TemplateAA]");
            PIElementTemplate templateAAA = client.ElementTemplate.GetByPath("\\\\MARC-PI2016\\WhatsNewInPIWebAPI2018\\ElementTemplates[TemplateAAA]");

            PIItemsElementTemplate baseTemplatesFromTemplateA = client.ElementTemplate.GetBaseElementTemplates(templateA.WebId);           
            PIItemsElementTemplate baseTemplatesFromTemplateAA = client.ElementTemplate.GetBaseElementTemplates(templateAA.WebId);
            PIItemsElementTemplate baseTemplatesFromTemplateAAA = client.ElementTemplate.GetBaseElementTemplates(templateAAA.WebId);

            Console.WriteLine($"There are {baseTemplatesFromTemplateA.Items.Count} base templates in Element Template A");
            Console.WriteLine($"There are {baseTemplatesFromTemplateAA.Items.Count} base templates in Element Template AA");
            Console.WriteLine($"There are {baseTemplatesFromTemplateAAA.Items.Count} base templates in Element Template AAA");

            PIItemsElementTemplate derivedTemplatesFromTemplateA = client.ElementTemplate.GetDerivedElementTemplates(templateA.WebId);
            PIItemsElementTemplate derivedTemplatesFromTemplateAA = client.ElementTemplate.GetDerivedElementTemplates(templateAA.WebId);
            PIItemsElementTemplate derivedTemplatesFromTemplateAAA = client.ElementTemplate.GetDerivedElementTemplates(templateAAA.WebId);

            Console.WriteLine($"There are {derivedTemplatesFromTemplateA.Items.Count} derived templates in Element Template A");
            Console.WriteLine($"There are {derivedTemplatesFromTemplateAA.Items.Count} derived templates in Element Template AA");
            Console.WriteLine($"There are {derivedTemplatesFromTemplateAAA.Items.Count} derived templates in Element Template AAA");


HTTP Request: GET - /piwebapi/elementtemplates/F1ETbvBs-758HkKnOzuxolJRlg-av52dhufE6iHqLJJk5faATUFSQy1QSTIwMTZcV0hBVFNORVdJTlBJV0VCQVBJMjAxOFxFTEVNRU5UVEVNUExBVEVTW1RFTVBMQVRFQV0/baseelementtemplates

HTTP Request GET - /piwebapi/elementtemplates/F1ETbvBs-758HkKnOzuxolJRlg-av52dhufE6iHqLJJk5faATUFSQy1QSTIwMTZcV0hBVFNORVdJTlBJV0VCQVBJMjAxOFxFTEVNRU5UVEVNUExBVEVTW1RFTVBMQVRFQV0/derivedelementtemplates


Running the application, the results are:



Allow reading annotations of a stream using the "associations" parameter


Some of the actions from the Stream and StreamSets controllers have the associations parameter added as an optional input. If this input is null, the values will be retrieved without their annotations. If the associations input is equal to "Annotations", then the annotations will be retrieved as well. Let's take a look at the example using the Stream.GetRecorded() method.



            PIPoint point1 = client.Point.GetByPath($"\\\\{piDataArchive.Name}\\SINUSOID");
            PIExtendedTimedValues valuesWithAnnotation = client.Stream.GetRecorded(point1.WebId, "Annotations");
            IEnumerable<List<PIStreamAnnotation>> annotationsList = valuesWithAnnotation.Items.Where(v => v.Annotated == true).Select(v => v.Annotations);
            foreach (List<PIStreamAnnotation> annotations in annotationsList)
                foreach (PIStreamAnnotation annotation in annotations)
                    Console.WriteLine($"Showing annotation: {annotation.Value}, {annotation.ModifyDate}");


HTTP Request: GET  /piwebapi/streams/F1DPQuorgJ0MskeiLb6TmEmH5gAQAAAATUFSQy1QSTIwMTZcU0lOVVNPSUQ/recorded?associations=Annotations


The result is shown below:




Allow showing all child attribute templates of an element template


On this demonstration, I have created a new attribute template called ParentAttribute on the TemplateAAA element template which has 2 child attributes templates named ChildAttribute1 and ChildAttribute2.



On previous versions of PI Web API it was not possible to access the child attribute templates of an attribute template. But this is possible in 2018 version:


            PIElementTemplate templateAAA = client.ElementTemplate.GetByPath("\\\\MARC-PI2016\\WhatsNewInPIWebAPI2018\\ElementTemplates[TemplateAAA]");
            PIItemsAttributeTemplate attributesWithChild = client.ElementTemplate.GetAttributeTemplates(templateAAA.WebId, showDescendants: true);
            foreach (PIAttributeTemplate attributeTemplate in attributesWithChild.Items)
                Console.WriteLine($"Showing attribute template -  Path: {attributeTemplate.Path}, HasChildren:{attributeTemplate.HasChildren}");


HTTP Request:  /piwebapi/elementtemplates/F1ETbvBs-758HkKnOzuxolJRlgoPGTVEM9d0S3M9GObE-kagTUFSQy1QSTIwMTZcV0hBVFNORVdJTlBJV0VCQVBJMjAxOFxFTEVNRU5UVEVNUExBVEVTW1RFTVBMQVRFQUFBXQ/attributetemplates?showDescendants=True


The results are shown below:



Filter attributes by trait and trait category


PI Web API allows you to get the attribute traits of an attribute by adding two new inputs to the GetAttributes() method: trait and traitCategory. I have created the attribute traits for the MainAttribute.


Please refer to the code below.


            PIAttribute mainAttribute = client.Attribute.GetByPath(@"\\MARC-PI2016\WhatsNewInPIWebAPI2018\AttributeTraits|MainAttribute");
            PIItemsAttribute attributeTraits = client.Attribute.GetAttributes(mainAttribute.WebId, trait: new List<string> { "Minimum", "Target" });



HTTP Request: GET -  /piwebapi/attributes/F1AbEbvBs-758HkKnOzuxolJRlgBG1jiF-E6BGbywAVXQAeEAUkg-GMAek0-aGWOEUJKR9QTUFSQy1QSTIwMTZcV0hBVFNORVdJTlBJV0VCQVBJMjAxOFxBVFRSSUJVVEVUUkFJVFN8TUFJTkFUVFJJQlVURQ/attributes?trait=Minimum&trait=Target


Please refer to the Attribute Trait page of the PI Web API help for more information.


Support health traits


PI Web API also allows you to get the health score and health status of an element by adding the same inputs of the previous example.



            PIElement mainElement = client.Element.GetByPath(@"\\MARC-PI2016\WhatsNewInPIWebAPI2018\AttributeTraits");
            PIItemsAttribute healthAttributeTraits = client.Element.GetAttributes(mainElement.WebId, trait: new List<string> { "HealthScore" });


The variable healthAttributeTraits will store the Health Score attribute of the element.


Please refer to the Attribute Trait page of the PI Web API help for more information.


Incrementally receive updates from streams with Stream Updates (CTP)


PI Web API comes with Stream Updates whose behavior is similar to the PI Web API channels but it uses the default HTTP requests instead of WebSockets. Here is the description of this feature on the PI Web API help.


"Stream Updates is a way in PI Web API to stream incremental and most recent data updates for PIPoints/Attributes on streams and streamsets without opening a websocket. It uses markers to mark the specific event in a stream where the client got the last updates and uses those to get the updates since that point in the stream."


In the example below, we will get the WebIDs of 3 PI Points and retrieve their new real time values every 30 seconds.



            PIPoint point1 = client.Point.GetByPath("\\\\marc-pi2016\\sinusoid");
            PIPoint point2 = client.Point.GetByPath("\\\\marc-pi2016\\sinusoidu");
            PIPoint point3 = client.Point.GetByPath("\\\\marc-pi2016\\cdt158");
            List<string> webIds = new List<string>() { point1.WebId, point2.WebId, point3.WebId };

            PIItemsStreamUpdatesRegister piItemsStreamUpdatesRegister = client.StreamSet.RegisterStreamSetUpdates(webIds);
            List<string> markers = piItemsStreamUpdatesRegister.Items.Select(i => i.LatestMarker).ToList();
            int k = 3;
            while (k > 0)
                PIItemsStreamUpdatesRetrieve piItemsStreamUpdatesRetrieve = client.StreamSet.RetrieveStreamSetUpdates(markers);
                markers = piItemsStreamUpdatesRetrieve.Items.Select(i => i.LatestMarker).ToList();
                foreach (PIStreamUpdatesRetrieve item in piItemsStreamUpdatesRetrieve.Items)
                    foreach (PIDataPipeEvent piEvent in item.Events)
                        Console.WriteLine("Action={0}, Value={1}, SourcePath={2}", piEvent.Action, piEvent.Value, item.SourcePath);



HTTP Request: POST - /piwebapi/streamsets/updates?webId=F1DPQuorgJ0MskeiLb6TmEmH5gAQAAAATUFSQy1QSTIwMTZcU0lOVVNPSUQ&webId=F1DPQuorgJ0MskeiLb6TmEmH5gAgAAAATUFSQy1QSTIwMTZcU0lOVVNPSURV&webId=F1DPQuorgJ0MskeiLb6TmEmH5g9AQAAATUFSQy1QSTIwMTZcQ0RUMTU4 H


HTTP Request: GET /piwebapi/streamsets/updates?marker=796081654a5648a3bf0d322fb58df518_0&marker=e2604da295584f498ce65976437b0c0f_0&marker=6f402964b31e4635aac2b87a68b79e60_0


The results are shown below:



Exposing properties of objects


There was some data model changes:

  • Expose 'ServerTime' property on objects of asset server, data server and system
  • Expose 'DisplayDigits', 'Span' and 'Zero' properties on objects of attribute and point
  • Expose 'DefaultUnitsNameAbbreviation' property on objects of attribute and attribute template


We can easily demonstrate that through the following code:


            PIPoint point = client.Point.GetByPath($"\\\\{piDataArchive.Name}\\SINUSOID");
            //Expose 'ServerTime' property on objects of asset server, data server and system
            PIItemsAssetDatabase dbs = client.AssetServer.GetDatabases(afServer.WebId);
            afServer = client.AssetServer.Get(afServer.WebId);
            Console.WriteLine($"PI AF Server ServerTime is {afServer.ServerTime}");

            //Expose 'DisplayDigits', 'Span' and 'Zero' properties on objects of attribute and point
            Console.WriteLine($"Sinusoid PIPoint: DisplayDigits={point.DisplayDigits}, Span={point.Span}, Zero={point.Zero}");

            //Expose 'DefaultUnitsNameAbbreviation' property on objects of attribute and attribute template
            PIAttribute attribute = client.Attribute.GetByPath(@"\\MARC-PI2016\AFPartnerCourseWeather\Cities\Chicago|Temperature");
            Console.WriteLine($"DefaultUnitsNameAbbreviation of the attribute is {attribute.DefaultUnitsNameAbbreviation}");


The results are shown below:





I hope you've found this blog post useful to learn the new features of PI Web API 2018. Please provide your feedback so we can repeat when future PI Web API releases are published.



A few years ago, we've announced the public PI Web API endpoint in order to:


  • Provide developers access to a PI System who may not be able to access PI otherwise
  • Create code samples against the public endpoint and to host them under OSIsoft organization on GitHub
  • Offer developers a playground to exercise with PI Web API
  • Create a streamlined way to offer datasets in a PI System


Although we were able to achieve the goals above, I felt that visualizing the data only through PI Web API is a challenge since common tools like PI System Explorer and PI Vision are not available to work with PI data stored on a remote PI Web API endpoint. Given this scenario, I've developed the PI Web API Data Reference which allows local attributes to access data from remote attributes through PI Web API endpoints. As a result, now I can see PI data from the public PI Web API endpoint within the PI System Explorer.


This custom data reference (CDR) was developed on top of the PI Web API client library for .NET Framework and PI AF SDK. The basic idea is to make HTTP requests against PI Web API and convert the responses into PI AF SDK objects which PSE will be able to process.




  • PI Web API 2017 R2+ installed within your domain using Kerberos or Basic Authentication. If you are using an older version, some methods might not work.
  • PI AF 2017 R2+
  • .NET Framework 4.6.2


GitHub repository

Please visit the GitHub repository for this project where you can find its source code, download the CDR under the GitHub release section and read about the new features added. There are two folders: src and dist. The src folder has the source code package. If you compile the VS Solution in the Release mode, the binaries will be created on the dist folder.  Due to the settings of the .gitgnore file, this folder is empty.


Video to get started:






  • Copy all files from the dist folder to %PIHOME%\PIWebApiDR
  • Register the CDR using the following command (you can also run register_plugin.bat):


"%pihome%\AF\regplugin" "OSIsoft.PIDevClub.PIWebApiDR.dll" "OSIsoft.PIDevClub.PIWebApiClient.dll" "Newtonsoft.Json.dll" /own:OSIsoft.PIDevClub.PIWebApiDR.dll /PISystem:PISYSTEMNAME 




  • Unregister the CDR using the following command (you can also run unregister_plugin.bat):


"%pihome%\AF\regplugin" -u "OSIsoft.PIDevClub.PIWebApiDR.dll" /PISystem:PISYSTEMNAME 


  • Delete all files from the %PIHOME%\PIWebApiDR folder.


ConfigString and ConfigStringEditor


The config string of this CDR has the following structure:RemoteDatabaseId={RemoteDatabaseId};WebId={WebId}

  • RemoteDatabaseId is the ID of the RemoteDatabase which is an AF Element with many attributes with the configuration to connect to a remote PI Web API.
  • WebId is the Web ID 2.0 of the remote AF Attribute.


The ConfigStringEditor (the window that is opened when you click on the "Settings..." button on PI System Explorer generally used to edit the attribute config string) does not allow the user to change the ConfigString of the attribute. It is used only to visualize the information. Please use the Remote Database Manager to delete and create databases mapping remote ones.




The remote databases are stored under the "OSIsoft Technology" root element of the Configuration AF database.




Remote Database Manager

The Remote Database Manager should be used to manage the remote database on the system as well as to create local AF databases mapped to remote AF databases through PI Web API. Below you can find a screenshot of this application that comes with the PI Web API Data Reference.



Visualizing PI data from remote AF databases locally in PSE

On the screenshot below, you can see a PI System Explorer accessing data from the "Universities Mapped" database. This database was created by the Remote Database Manager, mapping the "Universities" AF database from the public PI Web API.



Visualizing PI data from remote AF databases in PI Vision 3


This custom data reference works with PI Vision 3 as long as the following procedure is followed:

  1. On the PI Vision 3 machine, edit the file C:\ProgramData\OSIsoft\Coresight\PIDS_Settings.config  by adding the following key:   <add key="AFDRSetting:PI Web API" value="DisableInputs"/>
  2. Edit the PI Vision 3 web config (C:\Program Files\PIPC\PIVision) by making sure the file attribute of pidsSetting node is defined as:  <pidsSettings file="C:\ProgramData\OSIsoft\Coresight\PIDS_Settings.config" />
  3. Save and restart IIS


The procedure above will make sure that PI Vision will read data using the default PI AF SDK methods.


Features of the CDR in PI AF SDK


When writing our UnitTests, we have create a sample AF database with attributes using the PI Point DR and then created mapped AF database with PI Web API DR using the Remote Database Manager. Each test will get data from the PI Point DR and PI Web API DR and compare the results to make sure they are the same. Below you can find the features implemented on the PI Web API data reference:

  • Asynchronous
  • DataPipe (please refer to PIWebApiEventSource.cs)
  • InterpolatedValue
  • InterpolatedValues
  • InterpolatedValuesAtTimes
  • PlotValues
  • RecordedValue
  • RecordedValues
  • RecordedValuesAtTimes
  • Summary
  • Summaries
  • Bulk calls


If you use AFDataPIpes with Attributes with PI Web API Data Reference, the CDR will use PI Web API Channels to get new real-time values under the hood.



If you are having issues make sure:

  • .NET Framework 4.6.2 is installed on the computer registering the plugin.
  • Run the Remote Database Manager with administrative privileges.
  • Unblock the files after extracting them from the compressed file.





In case you are using Basic authentication, the username and password will be stored in fixed attributes. Although the password will be encoded, it won't be safe. Every user with read access to the Configuration database will be able to get the password of the remote PI Web API endpoint. For Kerberos authentication, the credentials won't be stored on the AF database.


This is not an official product. As described, there are security risks involved. Use it carefully.



I hope that the PI Community will benefit a lot from using the library as another tool to share data. Please test yourself and let me know if it works fine. I plan to record a video with some tips to use this great feature.



This is a MATLAB toolbox that integrates the PI System with MATLAB through PI Web API. With this toolbox you can retrieve PI data without having to generate the URL for each request. This version was developed on top of the PI Web API 2018 Swagger specification.


In the new upcoming 2018 release, PI Asset Analytics will introduce native connectivity to MATLAB enabling users to schedule and run their MATLAB functions fully integrated into their analyses. In other words, you will be able to integrate the PI System with MATLAB on production using a model that you have already built. This tool was developed for you to create new models with PI System data before using it on production.


Finally, this client library is read-only since only the HTTP GET request methods were added to the library.




  • PI Web API 2018 installed within your domain using Kerberos or Basic Authentication. If you are using an older version, some methods might not work.
  • MATLAB 2018a+


GitHub repository


Please visit the GitHub repository for this project where you can find its source code, download the toolbox and read about the new features added.




This MATLAB toolbox is not available on MATLAB central servers. You should download it directly from this GitHub repository on the release section.


Please use the command below to install the toolbox:




If the installation is successful, you should see this toolbox inside matlab.addons.toolbox.installedToolboxes:


toolboxes = matlab.addons.toolbox.installedToolboxes;


If you want to uninstall this toolbox, use the command below:






All the methods and classes from this MATLAB Toolbox are described on its documentation, which can be opened by typing on the console:


demo toolbox 'PI Web API client library for Matlab'




It is highly recommended to turn debug mode on in case you are using PI Web API 2017 R2+ in order to receive more detailed exception errors. This can be achieved by creating or editing the DebugMode attribute's value to TRUE from the System Configuration element.




Please refer to the following examples to understand how to use this library:


Create an instance of the piwebapi top level object using Basic authentication.


username = 'webapiuser';
useKerberos = false;
password = 'password'
baseUrl = '';
verifySsl = false;
client = piwebapi(baseUrl, useKerberos, username, password, verifySsl);


Only the Basic authentication is available on this initial version. Please make sure to set up PI Web API properly to make it compatible with this authentication method.

If you are having issues with your SSL certificate and you want to ignore this error, set verifySsl to false.


Retrieve data from the home PI Web API endpoint


pilanding = client.home.get();


Get the PI Data Archive object


dataServer = client.dataServer.getByName(serverName);


Get the PI Point, AF Element and AF Attribute objects


point = client.point.getByPath("\\PISRV1\sinusoid");
attribute = client.attribute.getByPath("\\PISRV1\Universities\UC Davis\Buildings\Academic Surge Building\Electricity|Demand");
element = client.element.getByPath("\\PISRV1\Universities\UC Davis\Buildings\Academic Surge Building\Electricity"); 


Get recorded, interpolated and plot values from a stream


webId = point1.WebId;
startTime = "*-10d";
endTime = "*";
interval = "1h";
intervals = 30;
maxCount = 100;
desiredUnits = '';
selectedFields = '';
associations = '';
boundaryType = '';
filterExpression = '';
includeFilteredValues = '';

recordedValues =, associations, boundaryType, desiredUnits, endTime, filterExpression, includeFilteredValues, maxCount, selectedFields, startTime);
interpolatedValues =, desiredUnits, endTime, filterExpression, includeFilteredValues, interval, selectedFields, startTime);
plotValues =, desiredUnits, endTime, intervals, selectedFields, startTime);


Get recorded, interpolated and plot values from a streamset in bulk


sortField = '';
sortOrder= '';

webIds = { point1.WebId, point2.WebId, point3.WebId, attribute.WebId};

recordedValuesInBulk = client.streamSet.getRecordedAdHoc(webIds, boundaryType, endTime, filterExpression, includeFilteredValues, maxCount, selectedFields, sortField, sortOrder, startTime);
interpolatedValuesInBulk = client.streamSet.getInterpolatedAdHoc(webIds, endTime, filterExpression, includeFilteredValues, interval, selectedFields, sortField, sortOrder, startTime);
plotValuesInBulk = client.streamSet.getPlotAdHoc(webIds, endTime, intervals, selectedFields, sortField, sortOrder, startTime);





If you want to create models to be used in production, please use this library and let me know if it works fine. You can send me an e-mail directly ( or leave a comment below.



After publishing the PI Web API client libraries on GitHub, I have received several enhancement requests (for .NET Standard, Java and Python) from our customers and partners. Some of them were added to the libraries!


Enhancements for the client library for .NET Standard


Migrated from RestSharp to HttpClient


Although there is no change for the end user,  RestSharp was replaced by the native HttpClient. The main reason is to use the CancellationTokenSource which will be commented on the next item. Also, HttpClient is available natively on .NET Standard so there is no need to download an extra NuGet package.


CancellationToken added for Async requests


Using the CancellationTokenSource allows you to cancel HTTP requests during a running operation. Below you can find an example:


Stopwatch watch = Stopwatch.StartNew();
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
PIItemsStreamValues bulkValues = null;
     Task t = Task.Run(async () =>
          bulkValues = await client.StreamSet.GetRecordedAdHocAsync(webId: webIds, startTime: "*-1800d", endTime: "*", maxCount: 50000, cancellationToken: cancellationTokenSource.Token);
     //Cancel the request after 1s
     Console.WriteLine("Completed task: Time elapsed: {0}s", 0.001 * watch.ElapsedMilliseconds);
catch (Exception)
     Console.WriteLine("Cancelled task: Time elapsed: {0}s", 0.001 * watch.ElapsedMilliseconds);


Fixed known issue on the Calculation controller


There was a known issue reported on GitHub when calling Calculation.GetAtTimes() method using expressions with comma. This was fixed so the code below works successfully!


string expression = "'sinusoid'*2 + 'cdt158'";
PITimedValues values = client.Calculation.GetAtTimes(webId: dataServer.WebId, expression: expression , time: new List<string>() { "*-1d" });

string expression2 = "'cdt158'+tagval('sinusoid','*-1d')";
PITimedValues values2 = client.Calculation.GetAtTimes(webId: dataServer.WebId, expression: expression2, time: new List<string>() { "*-1d" });


Enhancements for the client library for Java


PI Web API Batch was added in order to make more complex requests with better performance. You can find more information about PI Web API Batch here.


Added PI Web API Batch


Map<String, PIRequest> batch = new HashMap<String, PIRequest>();
PIRequest req1 = new PIRequest();
PIRequest req2 = new PIRequest();
PIRequest req3 = new PIRequest();

List<String> parameters = new ArrayList<>();
parameters.add("$.2.Content.WebId" );

List<String> parentIds = new ArrayList<>();

batch.put("1", req1);
batch.put("2", req2);
batch.put("3", req3);
Map<String, PIResponse> batchResponse = client.getBatch().execute(batch);

Object content1 = batchResponse.get("1").getContent();
Object content2 = batchResponse.get("2").getContent();
Object content3 = batchResponse.get("3").getContent();

JSON json = new JSON(client.getApiClient());
PIPoint pointBatch1 = json.deserialize(json.serialize(content1), new TypeToken<PIPoint>(){}.getType());
PIPoint pointBatch2 = json.deserialize(json.serialize(content2), new TypeToken<PIPoint>(){}.getType());
PIItemsStreamValue batchStreamValues = json.deserialize(json.serialize(content3), new TypeToken<PIItemsStreamValue>(){}.getType());



Added Web ID 2.0 client generation


Now, it is also possible to generate Web ID 2.0 without having to make an HTTP request against PI Web API. The library also provides a way to get information for a particular Web ID. Remember that this only works with PI Web API 2017 R2+.


PIDataServer dataServer = client.getDataServer().getByPath("\\\\MARC-PI2016", null, null);
PIPoint point = client.getPoint().getByPath("\\\\marc-pi2016\\sinusoid",null, null);
PIElement element = client.getElement().getByPath("\\\\MARC-PI2016\\CrossPlatformLab\\marc.adm",null, null);
PIAttribute attribute = client.getAttribute().getByPath( "\\\\MARC-PI2016\\CrossPlatformLab\\marc.adm|Heading",null,null);

WebIdInfo webIdInfo2 = client.getWebIdHelper().getWebIdInfo(attribute.getWebId());
WebIdInfo webIdInfo = client.getWebIdHelper().getWebIdInfo(element.getWebId());
WebIdInfo webIdInfo4 = client.getWebIdHelper().getWebIdInfo(point.getWebId());
WebIdInfo webIdInfo3 = client.getWebIdHelper().getWebIdInfo(dataServer.getWebId());

String web_id1 = client.getWebIdHelper().generateWebIdByPath("\\\\PISRV1\\CDF144_Repeated24h_forward", PIPoint.class, null);
String web_id2 = client.getWebIdHelper().generateWebIdByPath("\\\\PISRV1\\Universities\\UC Davis\\Buildings\\Academic Surge Building|Electricity Totalizer", PIAttribute.class, PIElement.class);


Available for downloading through JitPack


I've received a request on GitHub to publish the library on Maven Central. Since it is not an easy process, especially if you are not familiar, I've decided to publish it though JitPack.


If you want to use the Java library, please read the instructions here about how to retrieve the library without having to compile it locally.



Enhancements for the client library for Python



Added Kerberos as an authentication method


Robert Raesemann asked me in this blog post to make the client library for Python compatible with Kerberos authentication. Now it is possible to instantiate the PI Web API top level object as:


from osisoft.pidevclub.piwebapi.pi_web_api_client import PIWebApiClient
  client = PIWebApiClient("", useKerberos=True, verifySsl=False)



Added PI Web API Batch


PI Web API Batch was also added to Python.


  req1 = PIRequest()
  req2 = PIRequest()
  req3 = PIRequest()
  req1.method = "GET"
  req1.resource = "https://localhost/piwebapi/points?path=\\\\MARC-PI2016\\sinusoid"
  req2.method = "GET"
  req2.resource = "https://localhost/piwebapi/points?path=\\\\MARC-PI2016\\cdt158"
  req3.method = "GET"
  req3.resource = "https://localhost/piwebapi/streamsets/value?webid={0}&webid={1}"
  req3.parameters = ["$.1.Content.WebId", "$.2.Content.WebId"]
  req3.parent_ids = ["1", "2"]

  batch = {
"1": req1,
"2": req2,
"3": req3

  batchResponse = client.batch.execute(batch)
  point1 = client.api_client.deserialize_object(batchResponse["1"].content, 'PIPoint')
  point2 = client.api_client.deserialize_object(batchResponse["2"].content, 'PIPoint')
  itemsStreamValue = client.api_client.deserialize_object(batchResponse["3"].content, 'PIItemsStreamValue')


Thanks Rafael Borges for helping me with this task!



Optional parameters with default values


In this new version, you don't need to define all parameters of each method. Optional parameters have default values which are going to be used if they are not defined. Let's see an example:


piItemsStreamValues = client.streamSet.get_recorded_ad_hoc(webIds, start_time="*-3d", end_time="*",
                                                                   include_filtered_values=True, max_count=1000)



Added Web ID 2.0 client generation


Web ID 2.0 client generation was also added to the library. Here is an example:


pi_data_server_web_id = client.webIdHelper.generate_web_id_by_path("\\\\PISRV1", type(PIDataServer()), None)
  point1_web_id = client.webIdHelper.generate_web_id_by_path("\\\\PISRV1\\SINUSOID", type(PIPoint()))
  point2_web_id = client.webIdHelper.generate_web_id_by_path("\\\\PISRV1\\CDT158", type(PIPoint()))
  point3_web_id = client.webIdHelper.generate_web_id_by_path("\\\\PISRV1\\SINUSOIDU", type(PIPoint()))
  pi_attribute_web_id = client.webIdHelper.generate_web_id_by_path(
"\\\\PISRV1\\Universities\\UC Davis\\Buildings\\Academic Surge Building|Electricity Totalizer",
type(PIAttribute()), type(PIElement()))

  pi_element_web_id= client.webIdHelper.generate_web_id_by_path(
"\\\\PISRV1\\Universities\\UC Davis\\Buildings\\Academic Surge Building", type(PIElement()), None)


Available for downloading through PyPI (Python Package Index)


Just run the code below to download it:


pip install osisoft.pidevclub.piwebapi


You can find more information on the PyPI library page.




I hope you find value in those improvements. If you have an enhancement request concerning one of the client libraries, please let me know!


It is almost time to update to 2018!


Stay tuned for new updates and releases!

We are excited to present the PI World Innovation Hackathon 2018 winners!


DCP Midstream kindly provided a sample of their data with boosters and compressors information. Participants were encouraged to create killer applications for DCP Midstream by leveraging the PI System infrastructure.


The participants had 23 hours to create an app using any of the following technologies:

  • PI Server 2017 R2
  • PI Web API 2017 R2
  • PI Vision 2017  R2


Our judges evaluated each app based on their creativity, technical content, potential business impact, data analysis and insight and UI/UX. Although it is a tough challenge to create an app in 23 hours, six groups were able to finish their app and present to the judges!



1st place: Sonos PLAY 5 + Echo Dot, one year free subscription to PI Developers Club, one time free registration at OSIsoft PI World over the next 1 year

2nd place: Bose Quiet Comfort 35 (Series II), one year free subscription to PI Developers Club, 50% discount for registration at OSIsoft PI World over the next 1 year

3rd place: Vilros Raspberry Pi 3 Retro Arcade Gaming Kit + 5 USB Classic Controllers and one year free subscription to PI Developers Club



Without further do, here are the winners!


1st place - Oogway


The team members were: Kshitiz Jain, Matthew Wallace, Paurav Joshi and Diego Eduardo Mercadal



1st place.jpg


1st place winners interview.jpg


Team Oogway built a dashboard that enables the plants to make a more informed decision on the route boosters should take. This is done through providing the optimal plant for each booster and telling the current target of each booster. While giving the ability to drill down into each of the boosters to understand efficiency and anomalies.


The team used the following technologies:

  • Google Maps API embedded into PI Vision

Here are some screenshots presented by the Oogway team!


Compressor Page:






2nd place - <Insert Obligatory Gas Joke Here>


The team members were:  Rob Raesemann, Greg Busch, Lonnie Bowling and David Rodriguez


2nd place.jpg


They developed an app which utilizes PI AF event frames to generate leading data sets for further analysis. Utilizes PI Web API client library in Python to analyze and visualize data. Uses the PI AF SDK and Angular to provide visualization and interaction with event frames by end users. Explores novel voice interaction with end users.


The team used the following technologies:

  • Python
  • PI Web API and its client library for Python
  • Angular


Here are some screenshots presented by <Insert Obligatory Gas Joke Here>!





3rd place - Fantastic Four


The team members were: Xihui Zhang, Michael Baldauff, Jonathan Mejeur  and Syed Rehanrawos


3rd place.jpg


Team Fantastic Four developed an app which leverages features of PI Vision 4 in order to display IIOT results from a high level overview to multistate reporting.


The team used the following technologies:

  • PI Vision 4


Here is a screenshot presented by the Fantastic Four!


This is the material for the talk "Build PI Applications Faster with PI Web API Client Libraries" held during the PI World SF 2018 Developer Track.


GitHub - osimloeff/Building-Apps-Faster-Client-Libs-TechCon2018: Samples used on the "Building applications faster using…


It includes:

1. Visual Studio Solution

2. PowerPoint Presentation


Click "Download ZIP" on the right-side of the toolbar or fork and clone to your local repository.


This talk and source code have 10 examples:


  • Example 1 - Authentication
  • Example 2 - Retrieve basic PI Objects: PIElement, PIAttributes
  • Example 3 - Handling exceptions
  • Example 4 - Updating PI Point description
  • Example 5 - Retrieve data in bulk
  • Example 6 - Update data in bulk
  • Example 7 - PI Web API Batch
  • Examples 8 and 9 - Web ID 2.0 client generation
  • Example 10 - PI Web API Channels


This is the material for the "Developing Modern Web Apps Using Angular 5 and PI Web API " hands-on-lab held during the PI World SF 2018 Developer Track.


GitHub - osimloeff/Modern-Web-Technologies-TechCon2018


It includes:

1. Two Visual Studio Code Projects

2. Workbook

3. XML to be imported in PI AF

4. PowerPoint Presentation


Click "Download ZIP" on the right-side of the toolbar or fork and clone to your local repository.


The screenshot below shows the sample app used in this lab.



This lab has 5 exercises.

  • Exercises 1 and 2 are about retrieving the cities geolocation.
  • Exercises 3 is about getting live and performing calculations.
  • Exercise 4 will explain how integrate your app with PI Vision 3.
  • Exercise 5 is about PI Web API Batch


The Virtual Machine for this lab is available on OSIsoft Virtual Learning Environment whose link is below:



Although PI Vision 4 (2018) is not released yet, the participants of the Virtualization Virtual Hackathon 2018 need to learn the extensibility model of this product in order to raise their chances of creating a valuable custom symbol. As a result, I've decided to write the first blog post of the PI Vision 4 version of my "Developing the Google Maps custom symbol for PI Vision 3" blog post series.


The ultimate idea is that when the user drags an element and drops it on the PI Vision display, a Google Map will be created with a marker located according to the values of the latitude and longitude attributes of the dropped element. If another element is dropped on the map, another marker should be created accordingly.


This blog post (part 1) will focus on creating the map only, which is not something trivial.





Again, PI Vision 4 is not released yet. The hackathon participants are working with a preview version. Therefore, this library might not be compatible with the released version of PI Vision 4. I will update this article and library as soon as PI Vision 4 is released though.

PI Vision 4 public preview is planned to start at PI World San Francisco 2018. You will have the opportunity to test yourself this new extensibility model! The PI Vision 4 release is planned for Q4 2018.


Setting up your environment


I will comment this topic in details after PI Vision 4 is released. For now, the hackathon participants will access their Virtual Machine with the environment already set up. In order to develop your custom symbol, the following products are used:

  • Visual Studio Code
  • Google Chrome
  • Node.js/npm
  • Git


If you take a look at the Virtual Machine, you will realize that the PI Vision Extension Library Seed Project was already cloned to the C:\src\pi-vision-extensions folder.


Getting started developing the PI Vision symbol


Open the command prompt and navigate to the C:\src\pi-vision-extensions folder. Then type "code .". Visual Studio Code with the PI Vision Extensions project will be opened.


First rename the \src\example folder and its files. They should start with gmaps instead of example as shown on the screenshot below.




Don't worry, the gmaps-loader.service.ts will be created later. The code to get started for the gmaps.component.ts is below:


import { Component, OnChanges, OnInit } from '@angular/core';

  selector: 'gmaps',
  templateUrl: 'gmaps.component.html',
  styleUrls: ['gmaps.component.css']
export class GoogleMapsComponent implements OnInit, OnChanges {

  ngOnInit() {
  ngOnChanges(changes) {
    if ( {


The custom symbol is actually an Angular component whose decorator (@Component) is its metadata. It describes the selector, templateUrl and styleUrls. You can find more information about Angular in its official web site.


The content of HTML template for this component (gmaps.component.html) file is:


<div #gmap style="width:100%;height:100%"></div>


In order to create a map you just need a div HTML node. The rest is handled by JavaScript.


The next step is to write the Google Maps JavaScript code. Since we are using TypeScript, the definition of the Google Maps classes of needs to be downloaded and installed through the command below:


npm install --save @types/googlemaps




Getting started with Google Maps JavaScript API


Google provides a programming reference and samples for Google Maps JavaScript API, which were really useful to write this blog post. Let's take a look at the most basic example. Their HTML page has the following source code:


<!DOCTYPE html>
    <title>Simple Map</title>
    <meta name="viewport" content="initial-scale=1.0">
    <meta charset="utf-8">
      html, body {
        height: 100%;
        margin: 0;
        padding: 0;
      #map {
        height: 100%;
    <div id="map"></div>

var map;
function initMap() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: -34.397, lng: 150.644},
    zoom: 8

    <script src="" async defer></script>



Ok, we have some problems to solve:


  1. Editing the index file from the PI Vision 4 web site in order to load the Google Maps JavaScript API is not a recommended practice. The symbol will have to do this task.
  2. The second problem is that url which refers to the GMaps (Google Maps) library has the name of the callback function to be called after this library is loaded. How can we make this work within Angular 5?
  3. Users can add as many instances of this symbol as they want. On the other hand, the GMaps libraries needs to be loaded only once. How to make sure there won't be any conflict?



Solving problem 1: After some research, I found this interesting StackOverflow page, which allows us to dynamically load external JavaScript scripts using Typescript. After making some changes, here is the code that makes the trick:


const url = ""
let node = document.createElement('script');
node.src = url;
node.type = 'text/javascript';



Solving problem 2 and 3: If we define a function as property of the window JavaScript object, GMaps will be able to call it. Therefore, we have defined the window['gMapsCallback'] function as:


GoogleMapsLoader.promise = new Promise( resolve => {
    // Set callback for when google maps is loaded.
    window['gMapsCallback'] = (ev) => {

    let node = document.createElement('script');
    node.src = url;
    node.type = 'text/javascript';


We have created a JavaScript Promise that when it is solved it will define the window['gMapsCallback'] and then call the Google Maps JavaScript API which will call the window['gMapsCallback'] method. The beauty of this approach is that the promise is resolved only once which means that the Google Maps will be loaded also once.


An Angular service named GoogleMapsLoader is created to load the Google Maps JavaScript library with the following code snippet:



import { Injectable } from '@angular/core';

const url = ""
export class GoogleMapsLoader {
  private static promise;
  public static load() {
      // First time 'load' is called?
      if (!GoogleMapsLoader.promise) {
          // Make promise to load
          GoogleMapsLoader.promise = new Promise( resolve => {
              // Set callback for when google maps is loaded.
              window['gMapsCallback'] = (ev) => {

              let node = document.createElement('script');
              node.src = url;
              node.type = 'text/javascript';
      // Always return promise. When 'load' is called many times, the promise is already resolved.
      return GoogleMapsLoader.promise;


On the custom symbol (Angular component), this is how you would load the library by calling the GoolgeMapsLoader service:


      GoogleMapsLoader.load().then(res => {




With all these concepts and restrictions in mind, here is the final version of this blog post (part 1).


import { Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core';
import { GoogleMapsLoader } from './gmaps-loader.service'

  selector: 'gmaps',
  templateUrl: 'gmaps.component.html',
  styleUrls: ['gmaps.component.css']

export class GoogleMapsComponent implements OnInit, OnChanges {
  @ViewChild('gmap') gmapElement: any;
  private map : google.maps.Map

  constructor(private mapLoader : GoogleMapsLoader)


  ngOnInit() {

    GoogleMapsLoader.load().then(res => {
        console.log('GoogleMapsLoader.load.then', res);

  ngOnChanges(changes) {
    if ( {

  private initMap() {
    var mapProp = {
      center: new google.maps.LatLng(18.5793, 73.8143),
      zoom: 15,
      mapTypeId: google.maps.MapTypeId.ROADMAP
    }; = new google.maps.Map(this.gmapElement.nativeElement, mapProp);



When the custom symbol is created, the ngOnInit() method is called. This function calls initMap() which creates a map with a specific center and zoom. The ngOnChanges() method is called whenever the PI System receives a new value for the associated attribute or element.

The focus of this blog post is just to create a map for each custom symbol added to the display. This method will be used on the following parts of this blog post series.


The last step is to update the module.ts on the root folder with the following information:

Rename the ExampleComponent to GoogleMapsComponent which is present on the declaration, exports and entryComponents fields  of the NgModule declarator. Add the GoogleMapsLoader to the providers field which should contain all the services. In order to be successful you need to import those modules properly.


Finally, we need to rename the symbol properties on the ExtensionLibrary class. We have copied the google-maps.svg from this GitHub repository and pasted into the \src\assets\images folder. We have deleted all the items from the configProps array of the generalConfig object as at this point we are not interested in setting up the configuration pane of the symbol.


import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NgLibrary, SymbolType, SymbolInputType, ConfigPropType } from './framework';
import { LibModuleNgFactory } from './module.ngfactory';
import {GoogleMapsLoader} from './gmaps/gmaps-loader.service'
import { GoogleMapsComponent} from './gmaps/gmaps.component';

  declarations: [ GoogleMapsComponent ],
  imports: [ CommonModule ] ,
  providers:  [GoogleMapsLoader],
  exports: [ GoogleMapsComponent],
  entryComponents: [ GoogleMapsComponent ]
export class LibModule { }

export class ExtensionLibrary extends NgLibrary {
  module = LibModule;
  moduleFactory = LibModuleNgFactory;
  symbols: SymbolType[] = [
      name: 'gmaps-symbol',
      displayName: 'Google Maps Symbol',
      dataParams: { shape: 'single' },
      thumbnail: '^/assets/images/google-maps.svg',
      compCtor: GoogleMapsComponent,
      inputs: [
      generalConfig: [
          name: 'Google Maps Options',
          isExpanded: true,
          configProps: [ ]
      layoutWidth: 200,
      layoutHeight: 200


Save all files, run your local web pack server using "npm start run" and open PI Vision 4 using Google Chrome. In order to load the custom symbols, you need to go to the PI Vision landing page, select Options on the left pane and turn on the developer mode, according to the screenshot below:




Create a new display. Click on the Google Maps symbol on the left pane. Each click will create a new instance of the Google Maps custom symbol and add it to the PI Vision display. Make sure not only that the Google Maps symbol is added to the top-left pane but also that no exception is thrown when multiple symbols are added on a single display (please check the Google Chrome developer tools).




If you are a hackathon participant reading this blog post, I hope this material will help you create valueable custom symbols. If this is not the case, I hope you will have a good idea about the new extensibility model of PI Vision 4 and Angular 5.

PI World Innovation Hackathon 2018 

When: April 23th 9am (Day 0) - April 24th 8am (Day 1) 

Where: Embarcadero Room – Parc 55 Hotel


Join us at the Innovation Hackathon happening at PI World San Francisco 2018 to learn, network, and compete for prizes! Whether you are a PI System Developer, Architect, Integrator, Administrator, Business Analyst, or Data Scientist, you can enjoy this high-energy and vibrant event. OSIsoft will provide all of the tools, subject matter experts, and a ready-to-start environment. Just show up with your ideas and energy to develop a killer app in 23 hours! 



The data sponsor for the PI World 2018 SF Innovation Hackathon is DCP Midstream, which is one of the largest producers of natural gas liquids and one of the largest natural gas processing companies in the U.S. They gather and/or process about 12 percent of our nation’s gas supply.


The winners of this event will receive the following prizes:

1st place:

  • One-time 100% discount to attend the UC per team member*
  • Free 1-year subscription to PI Developers Club
  • Featured in PI Developers Club Community
  • Echo Dot + Sonos PLAY 5: Ultimate Wireless Smart Speaker for Streaming Music.

2nd place:

  • One-time 50% discount to attend the UC per team member*
  • Free 1-year subscription to PI Developers Club
  • Featured in PI Developers Club Community
  • Bose Quiet Comfort 35 (Series II)

3rd place:

  • Free 1-year subscription to PI Developers Club
  • Featured in PI Developers Club Community
  • Vilros Raspberry Pi 3 Retro Arcade Gaming Kit + 5 USB Classic Controllers


*Note: Discounts are for any OSIsoft PI World events in 2018 or 2019. TechCon labs and other training sessions are not included.



Register Today


Disclaimer: “You understand that OSIsoft strives to conduct business according to the highest ethical standards, and it may determine in its sole discretion that it is not appropriate to provide a prize to a winner under certain circumstances.”



I've recently published a blog post about generating the WebID on the client with the PI Web API client library for .NET Standard. It explains how the user can access the webIdHelper class in order to generate a WebID 2.0 on the client, without having to make an HTTP request. This class with the same methods and logic were added to the PI Web API client libraries for jQuery, Angular and AngularJS. This updated client library for AngularJS can also be used in PI Vision 3. As a result, the WebID 2.0 client generation feature is available when developing a custom PI Vision 3 symbol.


Note that those methods will only work if you are using PI Web API 2017 R2+.


Example in JavaScript (jQuery and AngularJS)


    var point1webId = piwebapi.webIdHelper.generateWebIdByPath("\\\\MARC-PI2016\\SINUSOID", "PIPoint");
    var point2webId = piwebapi.webIdHelper.generateWebIdByPath("\\\\MARC-PI2016\\CDT158", "PIPoint");
    var point3webId = piwebapi.webIdHelper.generateWebIdByPath("\\\\MARC-PI2016\\SINUSOIDU", "PIPoint");
    var piAttributeWebId = piwebapi.webIdHelper.generateWebIdByPath("\\\\MARC-PI2016\\CrossPlatformLab\\marc.adm|Heading", "PIAttribute", "PIElement");
    var piElementWebId = piwebapi.webIdHelper.generateWebIdByPath("\\\\MARC-PI2016\\CrossPlatformLab\\marc.adm", "PIElement");
    var piDataServerWebId = piwebapi.webIdHelper.generateWebIdByPath("\\\\MARC-PI2016", "PIDataServer");

    var piDataServer = null;
    var piAttribute = null;
    var piElement = null;
    piwebapi.dataServer.get(piDataServerWebId).then(function (response) {
        piDataServer =;
    piwebapi.attribute.get(piAttributeWebId).then(function (response) {
        piAttribute =;
    piwebapi.element.get(piElementWebId).then(function (response) {
        piElement =;

    var piAttributeWebIdInfo = piwebapi.webIdHelper.getWebIdInfo(piAttributeWebId);
    var piElementWebIdInfo = piwebapi.webIdHelper.getWebIdInfo(piElementWebId);
    var piDataServerWebIdInfo = piwebapi.webIdHelper.getWebIdInfo(piDataServerWebId); 




Example in TypeScript (Angular)


    let point1webId = this.piWebApiHttpService.webIdHelper.generateWebIdByPath("\\\\PISRV1\\SINUSOID",, null);
    let point2webId = this.piWebApiHttpService.webIdHelper.generateWebIdByPath("\\\\PISRV1\\CDT158",, null);
    let point3webId = this.piWebApiHttpService.webIdHelper.generateWebIdByPath("\\\\PISRV1\\SINUSOIDU",, null);
    let piAttributeWebId = this.piWebApiHttpService.webIdHelper.generateWebIdByPath("\\\\PISRV1\\Universities\\UC Davis\\Buildings\\Academic Surge Building|Electricity Totalizer",,;
    let piElementWebId = this.piWebApiHttpService.webIdHelper.generateWebIdByPath("\\\\PISRV1\\Universities\\UC Davis\\Buildings\\Academic Surge Building",, null);
    let piDataServerWebId = this.piWebApiHttpService.webIdHelper.generateWebIdByPath("\\\\PISRV1",, null);

    let piDataServer: PIDataServer = null;
    let piAttribute: PIAttribute = null;
    let piElement: PIElement = null;
    this.piWebApiHttpService.dataServer.get(piDataServerWebId).subscribe(res => {
        piDataServer = res;
    this.piWebApiHttpService.attribute.get(piAttributeWebId).subscribe(res => {
        piAttribute = res;
    this.piWebApiHttpService.element.get(piElementWebId).subscribe(res => {
        piElement = res;

    let piAttributeWebIdInfo = this.piWebApiHttpService.webIdHelper.getWebIdInfo(piAttributeWebId);
    let piElementWebIdInfo = this.piWebApiHttpService.webIdHelper.getWebIdInfo(piElementWebId);
    let piDataServerWebIdInfo = this.piWebApiHttpService.webIdHelper.getWebIdInfo(piDataServerWebId);  



The workflow of both examples above is:

  • Use the generateWebIdByPath method from the webIdHelper class to generate the WebID 2.0 of a PI object. You need to define the type of the object on the second input of this function. According to the PI object, the third input might be necessary to define the owner's type.
  • By calling the Get() method from Attribute, Element and DataServer controllers and reviewing the response, we will make sure that PI Web API understands the generated WebIDs. Otherwise, it will return an exception or an error status code in their response.
  • Finally, we call the getWebIdInfo() method from the webIdHelper class to get useful information about the WebID such as Path, ObjectID, ServerID, WebID Version and WebIDType.


You can learn more about those methods and the logic in the background in the .NET version of this blog post.


Example in a PI Vision 3 custom symbol


In this example we are creating a custom symbol on top of the PI Web API client library for AngularJS according to this blog post. This symbol gets the path of the element which is used to generate the WebID. Then the symbol calls the getSummary() method using the generated WebID from the Calculation controller. This method is returns the result of evaluating the expression over the time range. The JSON response is shown directly on the screen since our goal is to show the WebID generation.


Do not forget to update the angular-piwebapi-kerberos.js to version 1.1.1.


Please refer to the full code snippet below:



function loadPIWebApiModule() {
    var app = angular.module(APPNAME);
    var hasPiWebApiLoaded = (app.requires.indexOf("ngPIWebApi") > -1);

    if (hasPiWebApiLoaded == false) {

        var xhrObj = new XMLHttpRequest();'GET', "/PIVision/Scripts/app/editor/symbols/ext/libraries/angular-piwebapi-kerberos.min.js", false);
        var se = document.createElement('script');
        se.type = "text/javascript";
        se.text = xhrObj.responseText;
        piWebApiApp = angular.module('ngPIWebApi');
        var app = angular.module(APPNAME);

(function (PV) {
    function symbolVis() { }


    var definition = {
        typeName: 'piwebapi-adv-sample',
        displayName: 'PI Web API Client library advanced sample',
        datasourceBehavior: PV.Extensibility.Enums.DatasourceBehaviors.Multiple,
        inject: ['piwebapi'],
        getDefaultConfig: function () {
            return {
                DataShape: 'Table',
                Height: 400,
                Width: 400,
                MarkerColor: 'rgb(255,0,0)'
        visObjectType: symbolVis

    symbolVis.prototype.init = function init(scope, elem, piwebapi) {
        piwebapi.ConfigureInstance("", true);

        this.onDataUpdate = dataUpdate;
        this.onConfigChange = configChanged;
        this.onResize = resize;

        function configChanged(config, oldConfig) {
            console.log('configChange called');

        function resize(width, height) {
            console.log('resized called');

        scope.streamWebId = null;

        function dataUpdate(data) {
            if ((data == null) || (data.Rows.length == 0)) {

            var firstDataRow = data.Rows[0];
            if (firstDataRow.Path) {
                var path = firstDataRow.Path.substring(3, firstDataRow.Path.length);
                if (firstDataRow.Path.substring(0, 2) == "pi") {
                    path = "\\\\" + path.split("\\")[2]
                    scope.streamWebId = piwebapi.webIdHelper.generateWebIdByPath(path, "PIDataServer");
                if (firstDataRow.Path.substring(0, 2) == "af") {
                    path = path.split("|")[0];
                    scope.streamWebId = piwebapi.webIdHelper.generateWebIdByPath(path, "PIElement");


            if (scope.streamWebId != null) {

                calculationBasis = 'TimeWeighted';
                endTime = "*";
                expression = "2*'Latitude'";
                sampleInterval = undefined;
                sampleType = undefined;
                selectedFields = undefined;
                startTime = "*-1d";
                summaryDuration = undefined;
                summaryType = "total";
                timeType = undefined;
                webId = scope.streamWebId;
                piwebapi.calculation.getSummary(calculationBasis, endTime, expression, sampleInterval, sampleType, selectedFields, startTime, summaryDuration, summaryType, timeType, webId, null).then(function (response) {







Generating the Web ID 2.0 on the client provides a great performance improvement on your web application and PI Vision 3 custom symbols. Use this feature whenever is possible making sure your PI Web API version is compatible with this great feature.


Please share your comments and thoughts below!



I have already published a blog post about using PI Web API client library for AngularJS in PI Vision 2016 R2 and PI Vision 2017. Nevertheless, it was needed to modify the PI Vision source code, which was an important factor for our customers and partners not to use this great feature.


On this blog post, I will show you a new way to use PI Web API client library for AngularJS without changing the PI Vision 3 source code. This approach was tested in PI Vision 2017 and PI Vision 2017 R2.


Creating a custom symbol using the piwebapi service


First of all, open the browser and go to this GitHub repository. Download the source code package to a zip file. Within this file, copy the \dist\angular-piwebapi-kerberos.min.js file to %PIHOME64%\PIVision\Scripts\app\editor folder.


Since the PI Vision 3 source code won't be modified, we need to find a new way to:

  1. Detect if the library was already loaded
  2. If not, load the angular-piwebapi-kerberos.min.js script.
  3. Add the ngPIWebApi as a dependency of PI Vision.


As a result, all custom libraries using the PI Web API client library needs to start with the method below:


function loadPIWebApiModule() {
    var app = angular.module(APPNAME);
    var hasPiWebApiLoaded = (app.requires.indexOf("ngPIWebApi") > -1);

    if (hasPiWebApiLoaded == false) {
        var xhrObj = new XMLHttpRequest();'GET', "/PIVision/Scripts/app/editor/symbols/ext/libraries/angular-piwebapi-kerberos.min.js", false);
        var se = document.createElement('script');
        se.type = "text/javascript";
        se.text = xhrObj.responseText;
        piWebApiApp = angular.module('ngPIWebApi');
        var app = angular.module(APPNAME);



As soon as the custom library starts to load, it needs to call loadPIWebApiModule() method. The reason for that is we want to make sure that the script (angular-piwebapi-kerberos.min.js) is loaded before the symbolVis.prototype.init() method is called as the third input of this method is the PI Web API service (piwebapi Angular service variable). Let's take a look at the example provide in my previous blog post.


Create a new file name sym-piwebapi-basic-sample.js on the %PIHOME64%\PIVision\Scripts\app\editor\symbols\ext folder with the following content:


function loadPIWebApiModule() {
    var app = angular.module(APPNAME);
    var hasPiWebApiLoaded = (app.requires.indexOf("ngPIWebApi") > -1);

    if (hasPiWebApiLoaded == false) {
        var xhrObj = new XMLHttpRequest();'GET', "/PIVision/Scripts/app/editor/symbols/ext/libraries/angular-piwebapi-kerberos.min.js", false);
        var se = document.createElement('script');
        se.type = "text/javascript";
        se.text = xhrObj.responseText;
        piWebApiApp = angular.module('ngPIWebApi');
        var app = angular.module(APPNAME);

(function (PV) {
    function symbolVis() { }


    var definition = {
        typeName: 'piwebapi-basic-sample',
        displayName: 'PI Web API Client library basic sample',
        datasourceBehavior: PV.Extensibility.Enums.DatasourceBehaviors.Multiple,
        inject: ['piwebapi'],
        getDefaultConfig: function () {
            return {
                DataShape: 'Table',
                Height: 400,
                Width: 400,
                MarkerColor: 'rgb(255,0,0)'
        visObjectType: symbolVis

    symbolVis.prototype.init = function init(scope, elem, piwebapi) {
        piwebapi.ConfigureInstance("", true);
        console.log('Starting init scope: ' + scope);
        console.log('Starting init scope: ' + elem);

        this.onDataUpdate = dataUpdate;
        this.onConfigChange = configChanged;
        this.onResize = resize;
        function configChanged(config, oldConfig) {
            console.log('configChange called');

        function resize(width, height) {
            console.log('resized called');

        function dataUpdate(data) {
            piwebapi.home.get().then(function (response) {


Create a new file name sym-piwebapi-basic-sample-template.html on the %PIHOME64%\PIVision\Scripts\app\editor\symbols\ext folder with the following content:


    <br /><br />
    <p style="color: white;">{{data}}</p>


You can find more custom PI Vision symbols samples that inject piwebapi service here.


Time to test our custom library. Dragging any element and dropping it on the screen, we can see that JSON response from the main PI Web API endpoint.





I hope that using the PI Web API client library without changing the PI Vision 3 source code will make our customers and partners use this library even more. On my next update, the library will be able to build WebIDs client side.



PI Web API 2017 R2 comes with WebID 2.0, which allows you to generate the Web IDs on the client-side, without having to make an HTTP request.


This is actually an important performance improvement since if you wanted to get the current value of a PI Point using older versions of PI Web API, two requests were needed:

  • One request to get the WebID using the path as input.
  • One request to get the current value using the WebID.


By generating the Web ID on the client, only the second request is needed.


Reducing the number of HTTP request is an important step to improve the performance of you application.


The goals of this blog post are:


  • Teach you how to use the PI Web API client library for .NET Standard to generate Web IDs on the client.
  • View the information of a given Web ID.
  • Show you the source code and the unit tests so you can understand the logic and create something similar on other platforms.




I've referred to the articles and videos written and recorded by Christopher Sawyer below to get started.



I strongly suggest taking a look at them before continuing to read.


The WebIdHelper class


A new property called WebIdHelper was added to the PIWebApiClient, the top level object of the client library. This property is a WebIdHelper object with two methods:


  • GetWebIdInfo(string webId)
  • GenerateWebIdByPath(string webId, Type objectType, Type ownerType)



Getting the Web ID information


Getting the information of a given Web ID is very simple. Let's take a look at the AF attribute example:


PIAttribute piAttribute = client.Attribute.GetByPath(Constants.AF_ATTRIBUTE_PATH);
WebIdInfo piAttributeWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAttribute.WebId);


The first line retrieves the Web ID of an AF attribute through a HTTP request (old method). The second line gets the WebID information by returning an object from the WebIdInfo class. This class has the following properties:


  • Marker (string) --> The marker of the object.
  • ObjectID (Guid) --> The ID of the object.
  • ObjectType (Type) --> The type of the object.
  • OwnerID (Guid) --> The ID of the owner of the object.
  • OwnerType (Type) --> The type of the owner of the object.
  • Path (string) --> The path of the object.
  • PointID (int) --> The PointID of the PI Point. In this case the objectID won't be used.
  • ServerID (Guid) --> The ID of the AF Server or the PI Data Archive.
  • Version (int) --> The version of the Web ID. If it is using the Web ID 2.0, the version should be 1.
  • WebIdType (WebIdType) --> The WebIDType which could be Full, PathOnly, IDOnly, LocalDOnly, DefaultIDOnly.


If you take a look at the Specification Tables, you would realize that some objects have ObjectID, some objects don't. Some have OwnerID, some don't. PIDataServer and PIAssetServer don't have OwnerID and ObjectID but only the ServerID. After taking a look at all object types, we have realized that there are 5 categories of WebIDs according to the table below:


CategoryServer IDObject IDOwner IDOwner MarkerExamples
AXPIDataArchive and PI AssetServer

PIAnalysis, PIAnalysisCategory, PIAnalysisTemplate, PIAnalysisRulePlugIn, PIAttributeCategory, PIEventFrame, PITimeRulePlugIn, PISecurityIdentity,

PISecurityMapping, PITable, PITableCategory, PIUnit, PIUnitClass, PIAssetDatabase, PIElement, PIElementCategory, PIElementTemplate

DXXXXPIAnalysisRule, PIAttribute, PIAttributeTemplate, PIEnumerationValue, PITimeRule
EXX (PointID instead of ObjectID)PIPoint


NOTE: Since the client library doesn't have Notification objects, they were removed from the table above.


In all types of objects, the 4 first characters are processed the same way:

- First character: Web ID type

- Second character: Web ID version

- Third and fourth characters: Marker of the object type


The other characters are processed by the ProcessGuidAndPaths() method below:


        private void ProcessGuidsAndPaths(string webId, bool hasMarkerOwner, WebIdStringType webIdStringType, bool usePIPoint = false)
            string restWebId = webId.Substring(4);

            if (hasMarkerOwner == true)
                string markerOwner = restWebId.Substring(0, 1);
                restWebId = restWebId.Substring(1);

            if ((WebIdType == WebIdType.Full) || (WebIdType == WebIdType.IDOnly))
                string encodedServerID = restWebId.Substring(0, 22);
                ServerID = DecodeGUID(encodedServerID);
                restWebId = restWebId.Substring(22);

                if (webIdStringType == WebIdStringType.ThreeGuids)
                    string encodedOwnerID = restWebId.Substring(0, 22);
                    OwnerID = DecodeGUID(encodedOwnerID);
                    restWebId = restWebId.Substring(22);
                if ((webIdStringType == WebIdStringType.ThreeGuids) ||
                    (webIdStringType == WebIdStringType.TwoGuids))

                    if (usePIPoint == false)
                        string encodedObjectID = restWebId.Substring(0, 22);
                        ObjectID = DecodeGUID(encodedObjectID);
                        restWebId = restWebId.Substring(22);
                        string encodedObjectID = restWebId.Substring(0, 6);
                        PointID = DecodeInt(encodedObjectID);
                        restWebId = restWebId.Substring(6);


            if ((WebIdType == WebIdType.Full) || (WebIdType == WebIdType.PathOnly))
                string encodedPath = restWebId;
                Path = DecodeString(encodedPath);


Each type of object belongs to a different category. Each category calls the ProcessGuidsAndPaths() function according to the table below:


CategoryHow to call ProcessGuidsAndPaths
AProcessGuidsAndPaths(webId, false, WebIdStringType.OneGuid, false)
BProcessGuidsAndPaths(webId, false, WebIdStringType.TwoGuids, false)
CProcessGuidsAndPaths(webId, true, WebIdStringType.ThreeGuids, false)
DProcessGuidsAndPaths(webId, true, WebIdStringType.TwoGuids, false)
EProcessGuidsAndPaths(webId, false, WebIdStringType.TwoGuids, true)


We have executed a Unit test to make sure that no exception is thrown in this process and that the results are correct:


            string webIdType = "Full";
            PIAnalysis piAnalysis = client.Analysis.GetByPath(Constants.AF_ANALYSIS_PATH, null, webIdType);
            WebIdInfo piAnalysisWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAnalysis.WebId);
            Assert.AreEqual(Constants.AF_ANALYSIS_PATH.ToUpper().Substring(2), piAnalysisWebIdInfo.Path);
            Assert.AreEqual(piAnalysis.Id, piAnalysisWebIdInfo.ObjectID.ToString());

            PIAnalysisCategory piAnalysisCategory = client.AnalysisCategory.GetByPath(Constants.AF_ANALYSIS_CATEGORY_PATH, null, webIdType);
            WebIdInfo piAnalysisCategoryWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAnalysisCategory.WebId);
            Assert.AreEqual(Constants.AF_ANALYSIS_CATEGORY_PATH.ToUpper().Substring(2), piAnalysisCategoryWebIdInfo.Path);
            Assert.AreEqual(piAnalysisCategory.Id, piAnalysisCategoryWebIdInfo.ObjectID.ToString());

            PIAnalysisRule piAnalysisRule = client.AnalysisRule.GetByPath(Constants.AF_ANALYSIS_RULE_PATH, null, webIdType);
            WebIdInfo piAnalysisRuleWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAnalysisRule.WebId);
            Assert.AreEqual(Constants.AF_ANALYSIS_RULE_PATH.ToUpper().Substring(2), piAnalysisRuleWebIdInfo.Path);
            Assert.AreEqual(piAnalysisRule.Id, piAnalysisRuleWebIdInfo.ObjectID.ToString());

            PIAnalysisRulePlugIn piAnalysisRulePlugIn = client.AnalysisRulePlugIn.GetByPath(Constants.AF_ANALYSIS_RULE_PLUGIN_PATH, null, webIdType);
            WebIdInfo piAnalysisRulePlugInWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAnalysisRulePlugIn.WebId);
            Assert.AreEqual(Constants.AF_ANALYSIS_RULE_PLUGIN_PATH.ToUpper().Substring(2), piAnalysisRulePlugInWebIdInfo.Path);
            Assert.AreEqual(piAnalysisRulePlugIn.Id, piAnalysisRulePlugInWebIdInfo.ObjectID.ToString());

            PIAnalysisTemplate piAnalysisTemplate = client.AnalysisTemplate.GetByPath(Constants.AF_ANALYSIS_TEMPLATE_PATH, null, webIdType);
            WebIdInfo piAnalysisTemplateWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAnalysisTemplate.WebId);
            Assert.AreEqual(Constants.AF_ANALYSIS_TEMPLATE_PATH.ToUpper().Substring(2), piAnalysisTemplateWebIdInfo.Path);
            Assert.AreEqual(piAnalysisTemplate.Id, piAnalysisTemplateWebIdInfo.ObjectID.ToString());

            PIAssetDatabase piAssetDatabase = client.AssetDatabase.GetByPath(Constants.AF_DATABASE_PATH, null, webIdType);
            WebIdInfo piAssetDatabaseWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAssetDatabase.WebId);
            Assert.AreEqual(Constants.AF_DATABASE_PATH.ToUpper().Substring(2), piAssetDatabaseWebIdInfo.Path);
            Assert.AreEqual(piAssetDatabase.Id, piAssetDatabaseWebIdInfo.ObjectID.ToString());

            PIAssetServer piAssetServer = client.AssetServer.GetByPath(Constants.AF_SERVER_PATH, null, webIdType);
            WebIdInfo piAssetServerWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAssetServer.WebId);
            Assert.AreEqual(Constants.AF_SERVER_PATH.ToUpper().Substring(2), piAssetServerWebIdInfo.Path);
            Assert.AreEqual(piAssetServer.Id, piAssetServerWebIdInfo.ServerID.ToString());

            PIAttribute piAttribute = client.Attribute.GetByPath(Constants.AF_ATTRIBUTE_PATH, null, webIdType);
            WebIdInfo piAttributeWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAttribute.WebId);
            Assert.AreEqual(Constants.AF_ATTRIBUTE_PATH.ToUpper().Substring(2), piAttributeWebIdInfo.Path);
            Assert.AreEqual(piAttribute.Id, piAttributeWebIdInfo.ObjectID.ToString());

            PIAttributeCategory piAttributeCategory = client.AttributeCategory.GetByPath(Constants.AF_ATTRIBUTE_CATEGORY_PATH, null, webIdType);
            WebIdInfo piAttributeCategoryWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAttributeCategory.WebId);
            Assert.AreEqual(Constants.AF_ATTRIBUTE_CATEGORY_PATH.ToUpper().Substring(2), piAttributeCategoryWebIdInfo.Path);
            Assert.AreEqual(piAttributeCategory.Id, piAttributeCategoryWebIdInfo.ObjectID.ToString());

            PIAttributeTemplate piAttributeTemplate = client.AttributeTemplate.GetByPath(Constants.AF_ATTRIBUTE_TEMPLATE_PATH, null, webIdType);
            WebIdInfo piAttributeTemplateWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAttributeTemplate.WebId);
            Assert.AreEqual(Constants.AF_ATTRIBUTE_TEMPLATE_PATH.ToUpper().Substring(2), piAttributeTemplateWebIdInfo.Path);
            Assert.AreEqual(piAttributeTemplate.Id, piAttributeTemplateWebIdInfo.ObjectID.ToString());

            PIDataServer piDataServer = client.DataServer.GetByPath(Constants.PI_DATA_SERVER_PATH, null, webIdType);
            WebIdInfo piDataServerWebIdInfo = client.WebIdHelper.GetWebIdInfo(piDataServer.WebId);
            Assert.AreEqual(Constants.PI_DATA_SERVER_PATH.ToUpper().Substring(2), piDataServerWebIdInfo.Path);
            Assert.AreEqual(piDataServer.Id, piDataServerWebIdInfo.ServerID.ToString());

            PIElement piElement = client.Element.GetByPath(Constants.AF_ELEMENT_PATH, null, webIdType);
            WebIdInfo piElementWebIdInfo = client.WebIdHelper.GetWebIdInfo(piElement.WebId);
            Assert.AreEqual(Constants.AF_ELEMENT_PATH.ToUpper().Substring(2), piElementWebIdInfo.Path);
            Assert.AreEqual(piElement.Id, piElementWebIdInfo.ObjectID.ToString());

            PIElementCategory piElementCategory = client.ElementCategory.GetByPath(Constants.AF_ELEMENT_CATEGORY_PATH, null, webIdType);
            WebIdInfo piElementCategoryWebIdInfo = client.WebIdHelper.GetWebIdInfo(piElementCategory.WebId);
            Assert.AreEqual(Constants.AF_ELEMENT_CATEGORY_PATH.ToUpper().Substring(2), piElementCategoryWebIdInfo.Path);
            Assert.AreEqual(piElementCategory.Id, piElementCategoryWebIdInfo.ObjectID.ToString());

            PIElementTemplate piElementTemplate = client.ElementTemplate.GetByPath(Constants.AF_ELEMENT_TEMPLATE_PATH, null, webIdType);
            WebIdInfo piElementTemplateWebIdInfo = client.WebIdHelper.GetWebIdInfo(piElementTemplate.WebId);
            Assert.AreEqual(Constants.AF_ELEMENT_TEMPLATE_PATH.ToUpper().Substring(2), piElementTemplateWebIdInfo.Path);
            Assert.AreEqual(piElementTemplate.Id, piElementTemplateWebIdInfo.ObjectID.ToString());

            PIEnumerationSet piEnumerationSet = client.EnumerationSet.GetByPath(Constants.AF_ENUMERATION_SET_PATH, null, webIdType);
            WebIdInfo piEnumerationSetWebIdInfo = client.WebIdHelper.GetWebIdInfo(piEnumerationSet.WebId);
            Assert.AreEqual(Constants.AF_ENUMERATION_SET_PATH.ToUpper().Substring(2), piEnumerationSetWebIdInfo.Path);
            Assert.AreEqual(piEnumerationSet.Id, piEnumerationSetWebIdInfo.ObjectID.ToString());

            PIEnumerationValue piEnumerationValue = client.EnumerationValue.GetByPath(Constants.AF_ENUMERATION_VALUE_PATH, null, webIdType);
            WebIdInfo piEnumerationValueWebIdInfo = client.WebIdHelper.GetWebIdInfo(piEnumerationValue.WebId);
            Assert.AreEqual(Constants.AF_ENUMERATION_VALUE_PATH.ToUpper().Substring(2), piEnumerationValueWebIdInfo.Path);
            Assert.AreEqual(piEnumerationValue.Id, piEnumerationValueWebIdInfo.ObjectID.ToString());

            PIEventFrame piEventFrame = client.EventFrame.GetByPath(Constants.AF_EVENT_FRAME_PATH, null, webIdType);
            WebIdInfo piEventFrameWebIdInfo = client.WebIdHelper.GetWebIdInfo(piEventFrame.WebId);
            Assert.AreEqual(Constants.AF_EVENT_FRAME_PATH.ToUpper().Substring(2), piEventFrameWebIdInfo.Path);
            Assert.AreEqual(piEventFrame.Id, piEventFrameWebIdInfo.ObjectID.ToString());

            PIPoint piPoint = client.Point.GetByPath(Constants.PI_POINT_PATH, null, webIdType);
            WebIdInfo piPointWebIdInfo = client.WebIdHelper.GetWebIdInfo(piPoint.WebId);
            Assert.AreEqual(Constants.PI_POINT_PATH.ToUpper().Substring(2), piPointWebIdInfo.Path);
            Assert.AreEqual(piPoint.Id.ToString(), piPointWebIdInfo.PointID.ToString());

            PISecurityIdentity piSecurityIdentity = client.SecurityIdentity.GetByPath(Constants.AF_SECURITY_IDENTITY_PATH, null, webIdType);
            WebIdInfo piSecurityIdentityWebIdInfo = client.WebIdHelper.GetWebIdInfo(piSecurityIdentity.WebId);
            Assert.AreEqual(Constants.AF_SECURITY_IDENTITY_PATH.ToUpper().Substring(2), piSecurityIdentityWebIdInfo.Path);
            Assert.AreEqual(piSecurityIdentity.Id, piSecurityIdentityWebIdInfo.ObjectID.ToString());

            PISecurityMapping piSecurityMapping = client.SecurityMapping.GetByPath(Constants.AF_SECURITY_MAPPING_PATH, null, webIdType);
            WebIdInfo piSecurityMappingWebIdInfo = client.WebIdHelper.GetWebIdInfo(piSecurityMapping.WebId);
            Assert.AreEqual(Constants.AF_SECURITY_MAPPING_PATH.ToUpper().Substring(2), piSecurityMappingWebIdInfo.Path);
            Assert.AreEqual(piSecurityMapping.Id, piSecurityMappingWebIdInfo.ObjectID.ToString());

            PITable piTable = client.Table.GetByPath(Constants.AF_TABLE_PATH, null, webIdType);
            WebIdInfo piTableWebIdInfo = client.WebIdHelper.GetWebIdInfo(piTable.WebId);
            Assert.AreEqual(Constants.AF_TABLE_PATH.ToUpper().Substring(2), piTableWebIdInfo.Path);
            Assert.AreEqual(piTable.Id, piTableWebIdInfo.ObjectID.ToString());

            PITableCategory piTableCategory = client.TableCategory.GetByPath(Constants.AF_TABLE_CATEGORY_PATH, null, webIdType);
            WebIdInfo piTableCategoryWebIdInfo = client.WebIdHelper.GetWebIdInfo(piTableCategory.WebId);
            Assert.AreEqual(Constants.AF_TABLE_CATEGORY_PATH.ToUpper().Substring(2), piTableCategoryWebIdInfo.Path);
            Assert.AreEqual(piTableCategory.Id, piTableCategoryWebIdInfo.ObjectID.ToString());

            PIUnit piUnit = client.Unit.GetByPath(Constants.AF_UOM_PATH, null, webIdType);
            WebIdInfo piUnitWebIdInfo = client.WebIdHelper.GetWebIdInfo(piUnit.WebId);
            Assert.AreEqual(Constants.AF_UOM_PATH.ToUpper().Substring(2), piUnitWebIdInfo.Path);
            Assert.AreEqual(piUnit.Id, piUnitWebIdInfo.ObjectID.ToString());

            PIUnitClass piUnitClass = client.UnitClass.GetByPath(Constants.AF_UOM_CLASS_PATH, null, webIdType);
            WebIdInfo piUnitClassWebIdInfo = client.WebIdHelper.GetWebIdInfo(piUnitClass.WebId);
            Assert.AreEqual(Constants.AF_UOM_CLASS_PATH.ToUpper().Substring(2), piUnitClassWebIdInfo.Path);
            Assert.AreEqual(piUnitClass.Id, piUnitClassWebIdInfo.ObjectID.ToString());



Generating a Web ID given a path


Generating a Web ID on the client is a very simple task. Just call the client.WebIdHelper.GenerateWebIdByPath() method. Here are some examples:


            string webId = client.WebIdHelper.GenerateWebIdByPath(Constants.AF_DATABASE_PATH, typeof(PIAssetDatabase));
            string webId2 = client.WebIdHelper.GenerateWebIdByPath(Constants.AF_ATTRIBUTE_PATH, typeof(PIAttribute), typeof(PIElement));


The inputs of this function are:

  • The path of the object (required)
  • The type of the object (required)
  • The type of the owner of the object (optional)


The third input should only be used for objects that belong to the Category C and D. There is a method on the client library that validates this parameter which was developed according to the table specification:


      private void ValidateTypeAndOwnerType(Type type, Type ownerType)
            if (type == typeof(PIAttribute))
                if ((ownerType != typeof(PIElement)) && (ownerType != typeof(PIEventFrame)))
                    throw new WebIdException("PIAttribte owner type must be a PIElement or a PIEventFrame.");
            else if (type == typeof(PIAttributeTemplate))
                if ((ownerType != typeof(PIElementTemplate)))
                    throw new WebIdException("PIElementTemplate owner type must be a PIElementTemplate.");
            else if ((type == typeof(PIEnumerationSet)) && (type == typeof(PIEnumerationValue)))
                if ((ownerType != typeof(PIDataServer)) && (ownerType != typeof(PIAssetServer)))
                    throw new WebIdException("PIEnumerationSet and  PIEnumerationValue owner type must be a PIDataServer or PIAssetServer.");
            else if (type == typeof(PITimeRule))
                if ((ownerType != typeof(PIAnalysis)) && (ownerType != typeof(PIAnalysisTemplate)))
                    throw new WebIdException("PITimeRule owner type must be a PIAnalysis and PIAnalysisTemplate.");



The returned WebID is generated as follows:


  • First two charcaters: "P1" (PathOnly WebIDType and Version 1)
  • Two characters for the marker's object
  • One character for the marker's owner object (optional)
  • Encoded path (after deleting the first two backslashes)


     public string GenerateWebIdByPath(string path, Type type, Type ownerType = null)
            ValidateTypeAndOwnerType(type, ownerType);
            string marker = GetMarker(type);
            string ownerMarker = GetOwnerMarker(ownerType);

            if (path.Substring(0, 2) == "\\\\")
                path = path.Substring(2);
            string encodedPath = Encode(path.ToUpperInvariant());
            return string.Format("P1{0}{1}{2}", marker, ownerMarker, encodedPath);




I hope that this blog post helped you:

  • Generate the Web ID using this client library faster
  • Understand the main concepts of Web ID 2.0
  • Write code in other platforms to generate the Web ID client side.


If you have questions, please post them below!

Filter Blog

By date: By tag: