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

PI Developers Club

73 Posts authored by: Marcos Vainer Loeff Employee



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.




  • 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!

We have news about the PI Web API client libraries for .NET!


We have migrated our PI Web API client library for .NET Core to .NET Standard, since it is a set of APIs that all .NET platforms have to implement. As a result, this library is compatible with .NET Framework and .NET Core. The link of the new repository is here. The PI Web API client library for .NET Framework should only be used if you want to take advantage of PI Web API Channels which is not available on the .NET Standard version. Please read this blog post about this feature.


It has never been easier to install the PI Web API client library in your .NET projects. Now it is available on NuGet!!




You can find more information about this package on NuGet.


It is also very easy to install. Just open Package Manager and type:


Install-Package OSIsoft.PIDevClub.PIWebApiClient -Version 1.1.6


Please refer to this sample console app in order to get started faster!


If you have questions, please post it in the comments section.



Channels  is the PI Web API version of the AFDataPIpe feature of the AF SDK. It is a way to receive continuous updates about a stream or stream set. Rather than using a typical HTTP request, channels are accessed using the Web Socket protocol. There are numerous Web Socket client libraries in several languages available for use with channels.


The latest release of PI Web API client library for .NET Framework adds 3 methods to the PIWebApiClient.Channels class in order to use this feature client side using the IObserver pattern. This pattern was chosen due to the fact that PI Developers are used to using this pattern with AFDataPipe.


Methods added to the ChannelsApi class


PI Web API Channels can be used with 3 different URLs:

  • wss://myserver/piwebapi/streams/{webId}/channel (webId must be a stream
  • wss://myserver/piwebapi/streamsets/{webId}/channel (webId should be a streamset)
  • wss://myserver/piwebapi/streamsets/channel?webId={webId} (multiple streams)


A stream in PI Web API could be a PI Point or an AF Attribute with a data reference. A stream set is defined as a collection of streams. Streams inside a stream set can be independent, or share the same base element (e.g. element or event frame) or parent (e.g. parent attribute). Please review the PI Web API help for more information.


As a result, the following 3 methods were added to the ChannelsApi:



All of those methods returns a task still in execution. The reason it was designed this way is to avoid blocking the main thread of your application. The inputs of those methods are:


  • The webId of a stream or a stream,  or the list of webIds from multiple streams
  • A custom class that implements IObserver<PIItemsStreamValues> pattern. This is how you customize how the application would process new received values.
  • CancellationTokenSource which is used to cancel your task.




Let's start by creating our custom class which implements the IObserver<PIItemsStreamValues> interface.


    public class CustomChannelObserver : IObserver<PIItemsStreamValues>
        public void OnCompleted()

        public void OnError(Exception error)

        public void OnNext(PIItemsStreamValues value)
            foreach(PIStreamValues item in value.Items)
                foreach (PITimedValue subItem in item.Items)
                    Console.WriteLine("\n\nName={0}, Path={1}, WebId={2}, Value={3}, Timestamp={4}", item.Name, item.Path, item.WebId, subItem.Value, subItem.Timestamp);



Since this is just an example for you to refer to, this custom class will show the received values on the console. I suggest taking a look at the PIItemsStreamValues class. Its Items property has many PIStreamValues. The PIStreamValues has also the Items property which has many PITimedValue objects.


The code snippet of the main thread which will call the StartStreamSets method is below:


        private static void ChannelsExamples()
            PIWebApiClient client = new PIWebApiClient("", true);
            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 };

            CancellationTokenSource cancellationSource = new CancellationTokenSource();
            IObserver<PIItemsStreamValues> observer = new CustomChannelObserver();
            Task channelTask = client.Channel.StartStreamSets(webIds, observer, cancellationSource.Token);


This is how the code works:


  • Create a new instance of PI Web API using Kerberos authentication
  • Get the 3 WebIDs from the SINUSOID, SINUSOIDU and CDT158 points.
  • Create a list of those 3 WebIDs.
  • Instantiate a cancellationTokenSource and the custom class with the IObserver pattern.
  • Call StartStreamSets method which will return a task.
  • Wait 2 minutes using the Thread.Sleep() method. During this period all the events will be received through the OnNext() method from the custom class with the IObserver pattern.
  • Call cancellationTokenSource.Cancel() method.
  • Wait for the returned task to finish.


The new values are shown on the console:



Please refer to this sample application for more details of the implementation.




For all PI developers who are used to using IObserver pattern with AFDataPIpe, I think they will find pretty easy to use it with PI Web API Channels. Unfortunately, this feature is only available on the PI Web API client library for .NET Framework and not on the .NET Standard version due to the fact that WebSockets from .NET Core is not compatible with PI Web API Channels security.

When: March 19, 2018 - April 16, 2018

Where: Anywhere

Join us at our second Visualization Virtual Hackathon to learn, network, and compete for prizes. We will provide all the tools, access to subject matter experts, and a ready-to-start environment. Just show up with your ideas and energy to develop some amazing visualizations in this 4 week event!

The hackathon will focus around adding new and innovative extensions to the OSIsoft visualization platform, PI Vision. You are free to use any of your own data or use one of our provided environments that will include several Asset Based Example Kits. At the end, your application will be judged by a group of experts. The top applications will be awarded prizes, and you and your team will be publicly recognized for the achievement.

For questions and discussions about the event, please check out our Hackathons space in PI DevClub. This will be the best medium to have your questions answered. In case you prefer to discuss something privately about this event, please contact

Q: Who is this hackathon for?

A: If you are a PI System developer, architect, integrator, idea person, subject matter expert, data scientist, or a technologist interested in data visualization, this event is for you.

Q: How do I sign up? What is the flow of the event?

A: Once you decide to join the hackathon, we ask you to sign up you and your teammates (up to 4 people per team) on this sheet. You can then start the work on your project while engaging with us and the rest of the hackers in this space. We highly encourage you to continuously follow this space because we will be posting updates and news about the event there. You are expected to submit your entry and other requirements by the end date of the event through GitHub. We will judge the entries and announce the winners at PI World 2018 in San Francisco.

Q: What do I need to bring?

A: For this hackathon, OSIsoft will be hosting a sneak peek version of PI Vision 4.x on Microsoft Azure for all contestants to use. For development, you will be able to use your own development environment.

Q: How do I submit my entry?

A: All submissions will be done through PI Square. In order to submit your project you will need to create a blog post; you can save it as a draft and make it public at the end of the event. Your post should include:

  • A description of the general purpose of your project, including who and why someone would use it
  • A picture of your project in use
  • A video that demonstrates your project, is a nice screen capturing tool
  • A link to your GitHub repository where your source code is located. Please note that all source code must be published under the Apache 2.0 license.


Q: Is GitHub required?

A: Yes, to be accepted all contributions must be hosted on GitHub. We are making this a prerequisite to facilitate collaboration within the community. You or one of your team members will need a GitHub account, and you will need to create a new repository in your personal GitHub account to host your work. For more information about using GitHub, you can look at their contributing guidelines,


Q: How will the judging take place? A: Judging for the event will  be based on your video submission as well as the following criteria:

  • Creativity and Originality
  • Potential Business Impact
  • Technical Implementation
  • Data Analysis and Insight
  • UI/UX


We will also have a bonus category for best Data Science application at this year's hackathon, complete with its own prize.


Q: What will I win?

A: Each member of the winning teams will receive


First Place: Nest Thermostat, Google Home, 3 Google Home Minis, and a Chromecast Ultra.

Second Place: Google Home, 3 Google Home Minis, and a Chromecast Ultra.

Third Place: Google Home, 1 Google Home Mini, and a Chromecast Ultra.


Best Data Science App: Google Home

Q: Does this replace the traditional hackathon held at the UC?

A: No, we will still be holding our regular hackathon at the UC this year.


Q: How can I learn more?

A: Check out the recording of our webinar for more details about the Visualization Virtual Hackathon 2018. There will also be a sneak peek into PI Vision 4 extensibility on March 9th.

A Sneak Peek into the PI Vision 4 Extensibility Model


March 9, 2018

9:00 AM (PT)/12:00 PM (ET)/6:00 PM (ES)


Register now!


In this webinar you will be introduced to the new extensibility model coming in PI Vision 4 and we will walk through the creation of a custom symbol using OSIsoft's new developer seed project for PI Vision 4. There will be an opportunity to ask questions at the end. This webinar is initially intended to be a companion to the Visualization Virtual Hackathon 2018 webinar, but will give all viewers a sneak peek at PI Vision 4 and the new extensibility model.


We will cover:


  • PI Vision 4
  • PI Vision 4 developer seed project
  • Creating a new symbol for PI Vision 4
  • Q&A


The audience will have a chance to ask questions directly to the organizers of the event.



  • John Sintilas, Principal Software Developer, OSIsoft
  • Marcos Vainer Loeff, Sr. Technology Enablement Engineer, OSIsoft


We'd love to hear your comments and questions here in the comments or in the PI Developers Club forum!

Visualization Virtual Hackathon 2018


February 15, 2018

9:00 AM (PT)/12:00 PM (ET)/6:00 PM (ES)


Register now!


Learn, innovate, compete, and win big at OSIsoft's second Visualization Virtual Hackathon! If you are a PI System developer, architect, designer, or data professional this event is for you. In this webinar we will be announcing our second Visualization Virtual Hackathon. We will explain the flow of the event:


  • Agenda
  • Goals of the Hackathon
  • Registration
  • Rules
  • Resources and Documentation available
  • Judging criteria
  • Size of teams
  • Project submission
  • Prizes
  • Winners announcement


The audience will have a chance to ask questions directly to the organizers of the event.



  • John Sintilas, Principal Software Developer, OSIsoft
  • Marcos Vainer Loeff, Sr. Technology Enablement Engineer, OSIsoft


We'd love to hear your comments and questions here in the comments or in the PI Developers Club forum!

Filter Blog

By date: By tag: