Skip navigation
All Places > PI Developers Club > Blog > Author: Marcos Vainer Loeff

Introduction

 

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.

 

 

Disclaimer

 

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';


@Component({
  selector: 'gmaps',
  templateUrl: 'gmaps.component.html',
  styleUrls: ['gmaps.component.css']
})
export class GoogleMapsComponent implements OnInit, OnChanges {
  constructor()
  {
  }


  ngOnInit() {
  } 
  
  ngOnChanges(changes) {
    if (changes.data) {
     
    }
  }
  
}

 

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>
<html>
  <head>
    <title>Simple Map</title>
    <meta name="viewport" content="initial-scale=1.0">
    <meta charset="utf-8">
    <style>
      html, body {
        height: 100%;
        margin: 0;
        padding: 0;
      }
      #map {
        height: 100%;
      }
    </style>
  </head>
  <body>
    <div id="map"></div>
    <script>

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

    </script>
    <script src="https://maps.googleapis.com/maps/api/js?&callback=initMap" async defer></script>
  </body>
</html>

 

 

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 = "https://maps.google.com/maps/api/js?key=AIzaSyDUQhTeNplK37EX-mXdAB-zVuYDutE5c2w&callback=gMapsCallback"
let node = document.createElement('script');
node.src = url;
node.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(node);

 

 

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) => {
         resolve();
    };


    let node = document.createElement('script');
    node.src = url;
    node.type = 'text/javascript';
    document.getElementsByTagName('head')[0].appendChild(node);
});

 

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 = "https://maps.google.com/maps/api/js?key=AIzaSyDUQhTeNplK37EX-mXdAB-zVuYDutE5c2w&callback=gMapsCallback"
@Injectable()
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) => {
                  resolve();
              };


              let node = document.createElement('script');
              node.src = url;
              node.type = 'text/javascript';
              document.getElementsByTagName('head')[0].appendChild(node);
          });
      }
      // 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'


@Component({
  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);
        this.initMap();
    });
  } 


  ngOnChanges(changes) {
    if (changes.data) {
     
    }
  }


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


    this.map = 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';


@NgModule({
  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: [
        SymbolInputType.Data,
        SymbolInputType.PathPrefix
      ],
      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).

 

 

Conclusions

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.”

Introduction

 

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 = response.data;
    });
    piwebapi.attribute.get(piAttributeWebId).then(function (response) {
        piAttribute = response.data;
    });
    piwebapi.element.get(piElementWebId).then(function (response) {
        piElement = response.data;
    });




    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", PIPoint.name, null);
    let point2webId = this.piWebApiHttpService.webIdHelper.generateWebIdByPath("\\\\PISRV1\\CDT158", PIPoint.name, null);
    let point3webId = this.piWebApiHttpService.webIdHelper.generateWebIdByPath("\\\\PISRV1\\SINUSOIDU", PIPoint.name, null);
    let piAttributeWebId = this.piWebApiHttpService.webIdHelper.generateWebIdByPath("\\\\PISRV1\\Universities\\UC Davis\\Buildings\\Academic Surge Building|Electricity Totalizer", PIAttribute.name, PIElement.name);
    let piElementWebId = this.piWebApiHttpService.webIdHelper.generateWebIdByPath("\\\\PISRV1\\Universities\\UC Davis\\Buildings\\Academic Surge Building", PIElement.name, null);
    let piDataServerWebId = this.piWebApiHttpService.webIdHelper.generateWebIdByPath("\\\\PISRV1", PIDataServer.name, 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();
        xhrObj.open('GET', "/PIVision/Scripts/app/editor/symbols/ext/libraries/angular-piwebapi-kerberos.min.js", false);
        xhrObj.send('');
        var se = document.createElement('script');
        se.type = "text/javascript";
        se.text = xhrObj.responseText;
        document.getElementsByTagName('head')[0].appendChild(se);
        piWebApiApp = angular.module('ngPIWebApi');
        var app = angular.module(APPNAME);
        app.requires.push('ngPIWebApi');
    }
}




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


    loadPIWebApiModule();
    PV.deriveVisualizationFromBase(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("https://marc-web-sql.marc.net/piwebapi", 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)) {
                return;
            }


            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) {
                    scope.data = response.data;
                });
            }
        }
    }
    PV.symbolCatalog.register(definition);
})(window.PIVisualization);

 

 

 

 

Conclusions

 

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!

Introduction

 

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();
        xhrObj.open('GET', "/PIVision/Scripts/app/editor/symbols/ext/libraries/angular-piwebapi-kerberos.min.js", false);
        xhrObj.send('');
        var se = document.createElement('script');
        se.type = "text/javascript";
        se.text = xhrObj.responseText;
        document.getElementsByTagName('head')[0].appendChild(se);
        piWebApiApp = angular.module('ngPIWebApi');
        var app = angular.module(APPNAME);
        app.requires.push('ngPIWebApi');
    }
}

 

 

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();
        xhrObj.open('GET', "/PIVision/Scripts/app/editor/symbols/ext/libraries/angular-piwebapi-kerberos.min.js", false);
        xhrObj.send('');
        var se = document.createElement('script');
        se.type = "text/javascript";
        se.text = xhrObj.responseText;
        document.getElementsByTagName('head')[0].appendChild(se);
        piWebApiApp = angular.module('ngPIWebApi');
        var app = angular.module(APPNAME);
        app.requires.push('ngPIWebApi');
    }
}




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


    loadPIWebApiModule();
    PV.deriveVisualizationFromBase(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("https://marc-web-sql.marc.net/piwebapi", 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) {
                scope.data = response.data;
            });
        }
    }
    PV.symbolCatalog.register(definition);
})(window.PIVisualization);

 

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:

 

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

 

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.

 

 


Conclusions

 

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.

Introduction

 

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.

 

Materials

 

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
BXX

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

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

CXXXPIEnumerationSet
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);
                ProcessOwnerType(markerOwner);
                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);
                    }
                    else
                    {
                        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);
        }

 

Conclusions

 

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.

Introduction

 

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.

 

Example

 

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

 

    public class CustomChannelObserver : IObserver<PIItemsStreamValues>
    {
        public void OnCompleted()
        {
            Console.WriteLine("Completed");
        }


        public void OnError(Exception error)
        {
            Console.WriteLine(error.Message);
        }


        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("https://marc-web-sql.marc.net/piwebapi", 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);
            System.Threading.Thread.Sleep(120000);
            cancellationSource.Cancel();
            channelTask.Wait();
        }

 

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.

 

Conclusions

 

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 pidevclub@osisoft.com.

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, http://camstudio.org/ 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, https://github.com/osisoft/contributing.

 

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.

 

Speakers:

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

 

Speakers:

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

I am happy to announce that the client libraries below were updated using the new PI Web API swagger specification (2017 R2). All the new methods from PI Web API 2017 R2 are available for you to use on the client side! The X-Requested-With header was also added to work with CSRF defences.

 

 

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

Introduction

 

This is an R package that integrates the PI System with R through PI Web API. It was built with the PI Web API 2017 Swagger definition. With this package, you can retrieve PI data without having to generate the URL for each request.

 

You can visit the GitHub repository of this library here.

 

Requirements

 

  • R 3.4+

 

Installation

 

This R package is not available on CRAN. You should download it directly from this GitHub repository by using the devtools R package. If you don't have it installed, please use the command below:

 

install.packages("devtools")

 

Then, load the library and install the PI Web API R package with the install_github method:

 

library(devtools)
install_github("osimloeff/PI-Web-API-Client-R")

 

If the installation is successful, the command below will load the package:

 

library(piwebapi)

 

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

 

remove.packages("piwebapi")

 

 

Documentation

 

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

 

help(package="piwebapi") 

 

Examples

 

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

 

Create an intance of the piwebapi top level object.

 

Basic Authentication

 

useKerberos <- FALSE
username <- "myusername"
password <- "mypassword"
validateSSL <- TRUE
debug <- TRUE
piWebApiService <- piwebapi$new("https://webserver/piwebapi", useKerberos, username, password, validateSSL, debug)

 

Kerberos Authentication

 

useKerberos <- TRUE
username <- NULL
password <- NULL
validateSSL <- TRUE
debug <- TRUE
piWebApiService <- piwebapi$new("https://webserver/piwebapi", useKerberos, username, password, validateSSL, debug)

 

 

If you want to use basic authentication instead of Kerberos, set useKerberos to FALSE. If you are having issues with your SSL certificate and you want to ignore this error, set validateSSL to FALSE. If you want to receive a log about each HTTP request, set debug to TRUE.

 

 

Retrieve data from the main PI Web API endpoint

 

response1 = piWebApiService$home$get()

 

Get the PI Data Archive WebId

response2 = piWebApiService$dataServer$getByPath("\\\\piservername", "WebId")

 

Get current values in bulk using the StreamSet/GetValuesAdHoc

 

 

response3a = piWebApiService$point$getByPath("\\\\JUPITER001\\sinusoidu")
response3b = piWebApiService$point$getByPath("\\\\JUPITER001\\cdt158")
response3c = piWebApiService$point$getByPath("\\\\JUPITER001\\sinusoid")
webIds <- c(response3a$WebId, response3b$WebId, response3c$WebId)
response3d = piWebApiService$streamSet$getValuesAdHoc(webIds)

 

Retrieving PI data to an R data frame

 

 

response4a <- piWebApiService$data$getRecordedValues(path = "pi:\\\\PISRV1\\sinusoid", startTime = "y-200d", endTime = "t")
response4b <- piWebApiService$data$getRecordedValues(path = "pi:\\\\PISRV1\\sinusoid", startTime = "y-200d", endTime = "t", selectedFields = "items.value;items.timestamp")
response4c <- piWebApiService$data$getRecordedValues(path = "af:\\\\PISRV1\\UCDavisBuildings\\Buildings\\Buildings\\Academic Surge Building\\Electricity|Demand", startTime = "y-200d", endTime = "t")


response5a <- piWebApiService$data$getInterpolatedValues(path = "pi:\\\\PISRV1\\sinusoid", startTime = "y-2d", endTime = "t", interval = "1h")
response5b <- piWebApiService$data$getInterpolatedValues(path = "pi:\\\\PISRV1\\sinusoid", startTime = "y-2d", endTime = "t", interval = "1h", selectedFields = "items.value;items.timestamp")
response5c <- piWebApiService$data$getInterpolatedValues(path = "af:\\\\PISRV1\\UCDavisBuildings\\Buildings\\Buildings\\Academic Surge Building\\Electricity|Demand", startTime = "y-2d", endTime = "t", interval = "1h")


response6a <- piWebApiService$data$getPlotValues(path = "pi:\\\\PISRV1\\sinusoid", startTime = "y-2d", endTime = "t", intervals = 30)
response6b <- piWebApiService$data$getPlotValues(path = "pi:\\\\PISRV1\\sinusoid", startTime = "y-2d", endTime = "t", intervals = 30, selectedFields = "items.value;items.timestamp")
response6c <- piWebApiService$data$getPlotValues(path = "af:\\\\PISRV1\\UCDavisBuildings\\Buildings\\Buildings\\Academic Surge Building\\Electricity|Demand", startTime = "y-2d", endTime = "t", intervals = 30)


response7a <- piWebApiService$data$getMultipleRecordedValues(paths = paths, startTime = "y-200d", endTime = "t")
response7b <- piWebApiService$data$getMultipleRecordedValues(paths = paths, startTime = "y-200d", endTime = "t", selectedFields = "items.items.value;items.items.timestamp")


response8a <- piWebApiService$data$getMultipleInterpolatedValues(paths = paths, startTime = "y-200d", endTime = "t", interval = "1h")
response8b <- piWebApiService$data$getMultipleInterpolatedValues(paths = paths, startTime = "y-200d", endTime = "t", interval = "1h", selectedFields = "items.items.value;items.items.timestamp")


response9a <- piWebApiService$data$getMultiplePlotValues(paths = paths, startTime = "y-200d", endTime = "t", intervals = 30)
response9b <- piWebApiService$data$getMultiplePlotValues(paths = paths, startTime = "y-200d", endTime = "t", intervals = 30, selectedFields = "items.items.value;items.items.timestamp")

 

 

The path from the methods above should start with "pi:" (if your stream is a PI Point) or "af:" (if your stream is an AF attribute).

 

Create a PI Point

 

newPoint <- PIPoint(NULL, NULL, "SINUSOIDR", NULL, "12 Hour Sine Wave", "classic", "Float32", NULL, NULL, NULL, NULL, NULL)
response10 = piWebApiService$dataServer$createPoint("s0TJVKOA0Ws0KihcA8rM1GogUElGSVRORVNTLVNSVjI", newPoint)
Send values in bulk using the StreamSet/UpdateValuesAdHoc


timedValue1 <- PITimedValue(timestamp = "2017-04-26T17:40:54Z", value = 30)
timedValue2 <- PITimedValue(timestamp = "2017-04-27T17:40:54Z", value = 31)
timedValue3 <- PITimedValue(timestamp = "2017-04-26T17:40:54Z", value = 32)
timedValue4 <- PITimedValue(timestamp = "2017-04-27T17:40:54Z", value = 33)
t1 <- list(timedValue1, timedValue2)
t2 <- list(timedValue3, timedValue4)
s1 <- PIStreamValues(webId = webIds[1], items = t1);
s2 <- PIStreamValues(webId = webIds[2], items = t2);
values <- list(s1, s2)
response11 <- piWebApiService$streamSet$updateValuesAdHoc(values, "BufferIfPossible", "Replace");
Update the description from a PI Point


createdPoint <- piWebApiService$point$getByPath("\\\\PIFITNESS-SRV2\\SINUSOIDR")
updatePoint <- PIPoint()
updatePoint$Descriptor <- "12 Hour Sine Wave for R"
response12 <- piWebApiService$point$update(createdPoint$WebId, updatePoint)

 

Delete a PI Point

 

response13 <- piWebApiService$point$delete(createdPoint$WebId)

 

Using PI Batch to increase performance

 

getSinReq <- list(Method = "GET", Resource = "https://cross-platform-lab-uc2017.osisoft.com/piwebapi/points?path=\\\\pifitness-srv2\\sinusoid")
getCdtReq <- list(Method = "GET", Resource = "https://cross-platform-lab-uc2017.osisoft.com/piwebapi/points?path=\\\\pifitness-srv2\\cdt158")
getData <- list(Method = "GET", Resource = "https://cross-platform-lab-uc2017.osisoft.com/piwebapi/streamsets/value?webid={0}&webid={1}")
getData$Parameters <- c("$.sinu.Content.WebId", "$.cdt.Content.WebId")
getData$ParentIds <- c("sinu", "cdt")
batch <- list(sinu = getSinReq, cdt = getCdtReq, data = getData);
response14 <- piWebApiService$batch$execute(batch)
content(response11)

 

 

Create a SecurityEntry on an element

 

allowRight <- array(1:2)
allowRight[1] = "Read"
allowRight[2] = "ReadData"
denyRights <- array(1:3)
denyRights[1] = "Write"
denyRights[2] = "Execute"
denyRights[3] = "Admin"
securityEntry <- PISecurityEntry(securityIdentityName = "SwaggerIdentity", allowRights = as.list(allowRight), denyRights = as.list(denyRights))
response15 <- piWebApiService$element$createSecurityEntry(elementWebId, securityEntry, TRUE);

 

Get a SecurityEntry of an element

 

response16 <- piWebApiService$element$getSecurityEntries(elementWebId)

 

Update a SecurityEntry of an element

 

allowRight <- array(1)
allowRight[1] = "Read"
denyRights <- array(1:4)
denyRights[1] = "Write"
denyRights[2] = "Execute"
denyRights[3] = "Admin"
denyRights[4] = "ReadData"
securityEntry <- PISecurityEntry(allowRights = allowRight, denyRights = denyRights)
response17 <- piWebApiService$element$updateSecurityEntry("SwaggerIdentity", elementWebId, securityEntry, TRUE)

 

 

Final Remarks

 

We hope that data scientists will be able to integrate the PI System with R easier using this R package since it can return R data frame objects.

 

Please share your comments and suggestions below!

Introduction

 

Today we release our first version of the PI Web API client library for Python . The purpose of using this library is to make it easier the integration of a Python application with the PI System through PI Web API. This library is a client RESTful web service. All server methods from PI Web API 2017 are available on the library. As a result, you don't need to generate the URL in order to make an HTTP request. The library will generate for you automatically!

 

You can visit the GitHub repository of this library here.

 

Requirements

 

  • Python 2.7 or Python 3.4+

 

Installation

 

Before we start, it is good to mention that we don't recommend using this library with Anaconda. Try to use a clean Python instance instead.

 

pip install

 

If the python package is hosted on Github, you can install directly from Github

 

pip install git+https://github.com/osimloeff/PI-Web-API-Client-Python.git

 

You may need to run pip with root permission: sudo pip install git+https://github.com/osimloeff/PI-Web-API-Client-Python.git. If you are using Windows, remember to open the command prompt running as administrator. You must have Git installed on your machine.

 

Then import the package:

 

import osisoft.pidevclub.piwebapi 

 

Setuptools

 

Install via Setuptools.

 

python setup.py install --user

 

(or sudo python setup.py install to install the package for all users)

 

 

Then import the package:

import osisoft.pidevclub.piwebapi

 

 

This library was tested using PyCharm 2017.1.5.

 

 

Documentation

 

All classes and methods are described on the DOCUMENTATION.

 

Examples

 

Please check the test_main.py from this repository. Below there are also code snippets written in Python for you to get started using this library:

 

Create an instance of the PI Web API top level object.

 

    from osisoft.pidevclub.piwebapi.pi_web_api_client import PIWebApiClient
    client = PIWebApiClient("https://test.osisoft.com/piwebapi", False, "username", "password", True)  

 

Only Basic Authentication is available in this version. Therefore, the variable useKerberos should always be False. Do not forget to set the username and password accordingly.

 

Retrieving PI data to an Python pandas data frame

 

    df1 = client.data.get_recorded_values("pi:\\\\JUPITER001\\cdt158", None, None, "*-9d", None, None, None, None, "*-10d", None)df4 = client.data.get_multiple_recorded_values(["pi:\\JUPITER001\sinusoid", "pi:\\JUPITER001\sinusoidu", "pi:\\JUPITER001\cdt158"],None, "*", None, None, None, None, "*-1d", None)
    df2 = client.data.get_interpolated_values("pi:\\JUPITER001\\sinusoidu",None, "*", None, None, "2h", None, "*-1d", None)
    df3 = client.data.get_plot_values("pi:\\\\JUPITER001\\sinusoidu", None, "*", 10, None, "*-3d", None)
    df4 = client.data.get_recorded_values("pi:\\\\PISRV1\\sinusoid", None, None, "*", None, None, None, "items.value;items.timestamp", "*-1d", None)
    df5 = client.data.get_recorded_values("pi:\\\\PISRV1\\sinusoid", None, None, "*", None, None, None, "items.good;items.questionable;items.substituted", "*-1d", None)
    
    dfs1 = client.data.get_multiple_recorded_values(["pi:\\\\JUPITER001\\sinusoid", "pi:\\\\JUPITER001\\sinusoidu", "pi:\\\\JUPITER001\\cdt158", "af:\\\\JUPITER001\\Vitens\\Vitens\\Friesland province\\01 Production sites\\Production Site Noordbergum\\Distribution\\Quality|pH"],None, "*", None, None, None, None, "*-1d", None)
    dfs2 = client.data.get_multiple_interpolated_values(["pi:\\\\JUPITER001\\sinusoid", "pi:\\\\JUPITER001\\sinusoidu", "pi:\\\\JUPITER001\\cdt158", "af:\\\\JUPITER001\\Vitens\\Vitens\\Friesland province\\01 Production sites\\Production Site Noordbergum\\Distribution\\Quality|pH"], "*", None, None, "1d", None, "*-5d", None)
    dfs3 = client.data.get_multiple_plot_values(["pi:\\\\JUPITER001\\sinusoid", "pi:\\\\JUPITER001\\sinusoidu", "pi:\\\\JUPITER001\\cdt158", "af:\\\\JUPITER001\\Vitens\\Vitens\\Friesland province\\01 Production sites\\Production Site Noordbergum\\Distribution\\Quality|pH"], "*", 10, None, "*-1d", None)
    dfs4 = client.data.get_multiple_recorded_values(paths, None, "*", None, None, None, "items.items.value;items.items.timestamp", "*-1d", None)
    dfs5 = client.data.get_multiple_interpolated_values(paths, "*", None, None, "1h", "items.items.value;items.items.timestamp", "*-5d", None)

 

 

The path from the methods above should start with "pi:" (if your stream is a PI Point) or "af:" (if your stream is an AF attribute).

 

Get the PI Data Archive WebId

 

    dataServer = client.dataServer.get_by_path("\\\\JUPITER001", None);

 

Create a new PI Point

 

    newPoint = PIPoint()
    newPoint.name  = "SINUSOID_TEST"
    newPoint.descriptor = "Test PI Point for Python PI Web API Client"
    newPoint.point_class = "classic"
    newPoint.point_type = "float32"
    newPoint.future = False
    res = client.dataServer.create_point_with_http_info(dataServer.web_id, newPoint);    

 

Get PI Points WebIds

 

 

    point1 = client.point.get_by_path("\\\\JUPITER001\\sinusoid", None)
    point2 = client.point.get_by_path("\\\\JUPITER001\\cdt158", None)
    point3 = client.point.get_by_path("\\\\JUPITER001\\sinusoidu", None)

 

 

Get recorded values in bulk using the StreamSet/GetRecordedAdHoc

 

 

    webIds = list()
    webIds.append(point1.web_id);
    webIds.append(point2.web_id);
    webIds.append(point3.web_id);
    piItemsStreamValues = client.streamSet.get_recorded_ad_hoc(webIds, None, "*", None, True, 1000, None, "*-3d", None);

   

Send values in bulk using the StreamSet/UpdateValuesAdHoc

 

 

    streamValuesItems = PIItemsStreamValues()
    streamValue1 = PIStreamValues()
    streamValue2 = PIStreamValues()
    streamValue3 = PIStreamValues()

    value1 = PITimedValue()
    value2 = PITimedValue()
    value3 = PITimedValue()
    value4 = PITimedValue()
    value5 = PITimedValue()
    value6 = PITimedValue()

    value1.value = 2
    value1.timestamp = ("*-1d")
    value2.value = 3
    value2.timestamp = ("*-2d")
    value3.value = 4
    value3.timestamp = ("*-1d")
    value4.value = 5
    value4.timestamp = ("*-2d")
    value5.value = 6
    value5.timestamp = ("*-1d")
    value6.value = 7
    value6.timestamp = ("*-2d")


    streamValue1.web_id = point1.web_id
    streamValue2.web_id = point2.web_id
    streamValue3.web_id = point3.web_id


    values1 = list()
    values1.append(value1)
    values1.append(value2)
    streamValue1.items = values1


    values2 = list()
    values2.append(value3)
    values2.append(value4)
    streamValue2.items = values2


    values3 = list()
    values3.append(value5)
    values3.append(value6)
    streamValue3.items = values3


    streamValues = list()
    streamValues.append(streamValue1)
    streamValues.append(streamValue2)
    streamValues.append(streamValue3)
    response = client.streamSet.update_values_ad_hoc_with_http_info(streamValues, None, None)

 

 

Get an element and an attribute by path

 

    element = client.element.get_by_path("\\\\JUPITER001\\Universities\\UC Davis", None)
    attribute = client.attribute.get_by_path("\\\\JUPITER001\\Universities\\UC Davis\\Buildings|Campus Average EUI", "Name")

 

Final Remarks

 

It has never been easier to integrate the PI System with Python. By writing just some lines of code, it is very easy to retrieve PI data into Python through PI Web API.

 

Please share your comments and suggestions below!

Hello PI Geeks!

 

We are planning our next Hackathon at PI World 2018 where we expect tens of esteemed PI professionals, industry experts, and data scientists to compete. You can have your business challenge be the topic of the event which means there will be a whole group of engineers who will compete to add value to your business by solving one of your challenges.

 

We have been hosting several successful hackathons over the past few years (2017 SF, 2017 London, 2016 SF, 2016 Berlin, 2015 SF). In 2016, for example, the topic of the Programming Hackathon was Innovation Around Smart Cities. Data was sponsored by the San Diego International Airport and made available to our hackers. The executives from the airport were really happy with the final results of the hackathon mainly because:

 

  • They were inspired by the new creative apps and business models developed by our hackers, which could add a lot of value to their business.
  • They learned new ways to gain insight into the data they already had in their PI System.
  • They were able to detect where they could be more efficient in their industrial processes.

 

While starting to organize the PI World SF Hackathon 2018 we are looking to find our data sponsor. This is where you come in! We are seeking for a  customer who may be willing to share their data with us for the event. A good data sponsor typically has the following qualifications:

 

  • Owns a PI System with AF already in place
  • Has a few data-oriented high level business challenges or aspirations
  • Has at least tens of assets and many hundreds of data streams in place
  • Has at least 1 year of historical data
  • Has sampling rate of at least several samples a minute on the majority of the tags
  • Is willing to share their data with us – We are willing to consider an anonymized/obfuscated version of the dataset as well

 

In case you are interested becoming the new data sponsor for the Programming Hackathon, please don’t hesitate to contact me by e-mail (mloeff@osisoft.com).

The Technology Enablement team (okay, mainly Marcos) has published an interesting number of PI Web API client libraries on GitHub, I have created this page so you can have quick access to those bits:

 

PI Web API client library for .NET Framework

PI Web API client library for .NET Core

PI Web API client library for VBA

PI Web API client library for PHP

PI Web API client library for Java and Android

PI Web API client library for jQuery

PI Web API client library for AngularJS

PI Web API client library for Angular 4

PI Web API client library for Python (still in development)

PI Web API client library for R (still in development)

PI Web API client library for Go

 

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

 

DISCLAIMER: The GitHub repositories are examples of interesting things you may do with PI Developer Technologies.  However, these client libraries are not official OSIsoft products.  You are free to use the code, not use the code, or even modify the code as required by your own applications.

Filter Blog

By date: By tag: