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

Introduction

 

Today we release our first version of the PI Web API client library for Angular 4 (the newest version of AngularJS) . The purpose of using this library is to make it easier the integration of an Angular web 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.

 

You can visit the web page about this npm package here.

 

Although this library was tested with Angular 4, it should also be compatible with Angular 2.

 

The version for AngularJS was already released.

 

What is Angular?

 

According to the Angular official documentation:

 

"Angular is a platform that makes it easy to build applications with the web. Angular combines declarative templates, dependency injection, end to end tooling, and integrated best practices to solve development challenges. Angular empowers developers to build applications that live on the web, mobile, or the desktop."

 

Requirements

 

  • NodeJS (npm is going to be used to download the library)
  • Text Editor (Visual Studio Code is recommended)
  • Angular 4+

 

Installation

 

To install this library, run:

 

npm install angular-piwebapi --save

 

Consuming your library

 

Update your Angular AppModule by adding the PIWebAPIService as a provider:

 

import { PIWebAPIService } from 'angular-piwebapi';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
   AppComponent
  ],
  imports: [
   BrowserModule,
   FormsModule,
   HttpModule
  ],
  providers: [PIWebAPIService],
  bootstrap: [AppComponent]
})
export class AppModule { }

 

 

Documentation

 

All classes and methods are described on the DOCUMENTATION.

 

Examples

Please check the sample_main.ts from this repository. Below there are also code snippets written in Angular 4 for you to get started using this library:

 

Set up the instance of the PI Web API top level object.

If you want to use basic authentication instead of Kerberos, set useKerberos to false and set the username and password as shown below:

 

Basic authentication

 

    this.piWebApiHttpService.configureInstance("https://devdata.osisoft.com/piwebapi/", false, "username", "password");

 

Kerberos authentication

 

    this.piWebApiHttpService.configureInstance("https://devdata.osisoft.com/piwebapi/", true);

 

If you want to test if it connects, just execute the code below:

 

    this.piWebApiHttpService.home.get().subscribe(res => {
        console.log(res);
    }, error => {
        console.log(error.json());
    });

 

Get the PI Data Archive WebId

 

    this.piWebApiHttpService.dataServer.getByPath('\\\\SERVERNAME').subscribe(res => {
        console.log(res); 
    }, error => {
        console.log(error.json());
    });

 

Create a new PI Point

 

    var pointName = "SINUSOID_TEST74" + Math.trunc(100000*Math.random());
    var newPoint = new PIPoint(null, null, pointName, null, "Test PI Point for Angular PI Web API Client", "classic", "float32", null, null, null, false);    
    this.piWebApiHttpService.dataServer.createPoint(res.WebId, newPoint).subscribe(res => {
console.log(res);
}, error => {
console.log(error.json());
});     

 

 

Get PI Point WebId

 

    this.piWebApiHttpService.point.getByPath("\\\\MARC-PI2016\\sinusoid").subscribe(res => {
        console.log(res);
    }, error => {
        console.log(error.json());
    });

 

Get recorded values in bulk using the StreamSet/GetRecordedAdHoc

 

    var point1webId = "P0QuorgJ0MskeiLb6TmEmH5gAQAAAATUFSQy1QSTIwMTZcU0lOVVNPSUQ";
    var point2webId = "P0QuorgJ0MskeiLb6TmEmH5gAgAAAATUFSQy1QSTIwMTZcU0lOVVNPSURV";
    var point3webId = "P0QuorgJ0MskeiLb6TmEmH5g9AQAAATUFSQy1QSTIwMTZcQ0RUMTU4";




    var webIds = []
    webIds.push(point1webId);
    webIds.push(point2webId);
    webIds.push(point3webId);


    this.piWebApiHttpService.streamSet.getRecordedAdHoc(webIds, null, "*", null, true, 1000, null, "*-3d", null).subscribe(res => {
        console.log(res);
    }, error => {
        console.log(error.json());
    }); 

 

Send values in bulk using the StreamSet/UpdateValuesAdHoc

 

    let streamValuesItems : PIItemsStreamValues = new PIItemsStreamValues()
    let streamValue1 : PIStreamValues = new PIStreamValues()
    let streamValue2 : PIStreamValues = new PIStreamValues()
    let streamValue3 : PIStreamValues = new PIStreamValues()


    let value1 : PITimedValue = new PITimedValue()
    let value2 : PITimedValue = new PITimedValue()
    let value3 : PITimedValue = new PITimedValue()
    let value4 : PITimedValue = new PITimedValue()
    let value5 : PITimedValue = new PITimedValue()
    let value6 : PITimedValue = new 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.WebId = point1webId
    streamValue2.WebId = point2webId
    streamValue3.WebId = point3webId


    var values1 = [];
    values1.push(value1)
    values1.push(value2)
    streamValue1.Items = values1


    var values2 = [];
    values2.push(value3)
    values2.push(value4)
    streamValue2.Items = values2


    var values3 = [];
    values3.push(value5)
    values3.push(value6)
    streamValue3.Items = values3


    var streamValues = []
    streamValues.push(streamValue1)
    streamValues.push(streamValue2)
    streamValues.push(streamValue3)
    this.piWebApiHttpService.streamSet.updateValuesAdHoc(streamValues, null, null).subscribe(res => {
        console.log(res);
    }, error => {
        console.log(error.json());
    });

 

Using PI Web API Batch

 

 var pirequest = {};
    pirequest["4"] = {
        "Method": "GET",
        "Resource": "https://marc-web-sql.marc.net/piwebapi/points?path=\\\\MARC-PI2016\\sinusoid",
        "Headers": {
            "Cache-Control": "no-cache"
        }
    };
    pirequest["5"] = {
        "Method": "GET",
        "Resource": "https://marc-web-sql.marc.net/piwebapi/points?path=\\\\MARC-PI2016\\cdt158",
        "Headers": {
            "Cache-Control": "no-cache"
        }
    };
    pirequest["6"] = {
        "Method": "GET",
        "Resource": "https://marc-web-sql.marc.net/piwebapi/streamsets/value?webid={0}&webid={1}",
        "Parameters": [
            "$.4.Content.WebId",
            "$.5.Content.WebId"
        ],
        "ParentIds": [
            "4",
            "5"
        ]
    };
    this.piWebApiHttpService.batch.execute(pirequest).subscribe(res => {
        console.log(res);
    }, error => {
        console.log(error.json());
    }); 

 

 

If you know the name of the action and controller, you probably need to make a call similar to:

 

PIWebAPIService.{controllerName}.{actionName}(inputs);

 

Please read the PI Web API programming reference to find out the names of the actions, controllers and inputs.

 

If you want to find out the order of the inputs, please refer to DOCUMENTATION and search for the method you want to use.

 

Developing a modern app with PI Web API client library for Angular

 

Do you remember my old blog post about Using PI Web API with Angular 2?

 

On that blog post, we have created a PIWebAPIService class in order to make HTTP requests against PI Web API.

 

This time let's rewrite the file app.component.ts in order to use the library methods instead. This is a good example about how this library should be used after downloading it with npm and updating the file app.module.ts.

 

import { Component } from '@angular/core';
import { PIWebAPIService, PIItemsStreamValues, PIPoint, PITimedValue, PIStreamValues } from 'angular-piwebapi';




export class ComboboxOption {
  value: boolean;
  name: string;
}


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  requestMode = true;
  piServerName: string;
  piPointName: string;
  startTime: string;
  endTime: '*';
  interval: '1h';
  yesOrNoOptions: ComboboxOption[] = [{ 'value': true, 'name': 'Yes' }, { 'value': false, 'name': 'No' }];
  getSnap: ComboboxOption = this.yesOrNoOptions[0];
  getRec: ComboboxOption = this.yesOrNoOptions[0];
  getInt: ComboboxOption = this.yesOrNoOptions[0];
  piServerData: any;
  piServerExistsValue: boolean;
  piServerError: any;
  piPointData: any;
  piPointExistsValue: boolean;
  webId: string;
  snapshotData: any;
  snapshotError: any;
  recordedData: any;
  interpolatedData: any;
  recordedError: any;
  interpolatedError: any;
  piPointError: any;


  constructor(private piWebApiHttpService: PIWebAPIService) { }


  defaultValues(): void {
    this.piServerName = 'PISRV1';
    this.piPointName = 'SINUSOID';
    this.startTime = '*-1d';
    this.endTime = '*';
    this.interval = '1h';
    this.getSnap = this.yesOrNoOptions[0];
    this.getRec = this.yesOrNoOptions[0];
    this.getInt = this.yesOrNoOptions[0];
  }
  
  // get data by making http calls
  getData(): void {
    this.piWebApiHttpService.configureInstance("https://devdata.osisoft.com/piwebapi", false, "webapiuser", "!try3.14webapi!");


    // switch div to display the results
    this.requestMode = false;
    // all HTTP requests are done through the  piWebApiHttpService factory object
    this.piWebApiHttpService.dataServer.getByPath('\\\\' + this.piServerName)  
      .subscribe(res => {
        this.piServerData = res;
        this.piServerExistsValue = true;
      },
      error => {
        this.piServerError = error;
        this.piServerExistsValue = false;
      });




      this.piWebApiHttpService.point.getByPath('\\\\' + this.piServerName + '\\' + this.piPointName, null, null).subscribe(res => {
        this.piPointData = res
        this.piPointExistsValue = true
        //in case of success, we will get the webId of the PI Point which will be used by other requests
        this.webId = res.WebId;
        this.piWebApiHttpService.stream.getValue(this.webId).subscribe(res => {
            //Response of the snapshot is stored on the snapshotData
            this.snapshotData = res
        }, error => {
            this.snapshotError = error


        });


        this.piWebApiHttpService.stream.getRecorded(this.webId, null, null, this.endTime, null, null, null, null, this.startTime).subscribe(res => {
            this.recordedData = res
          }, error => {
            this.recordedError = error
        });
        this.piWebApiHttpService.stream.getInterpolated(this.webId, null, this.endTime, null, null, this.interval, null, this.startTime, null).subscribe(res => {
            this.interpolatedData = res
          }, error => {
            this.interpolatedError = error.json();
        });
      }, error => {
        this.piPointError = error.data
        this.piPointExistsValue = false
    });
  }
}

 

 

Final Remarks

 

It is a great enhancement the fact that you can download this library through npm. I hope that with this new release, developing modern apps on top of the PI System will become even easier!

 

Please share your comments and suggestions below!

Introduction

 

The PI Web API 2017 release has come with lots of great new features. One of the them is the Swagger specification which is a JSON string that allows you to generate client-side libraries in many different programming languages including Java, C#, JavaScript AngularJS etc...

 

I have already published the PI Web API client library for AngularJS. Nevertheless, there are web developers who still prefer jQuery and that are not familiar with AngularJS. Since the code is very similar (after all everything is written in Typescript which is compiled into JavaScript), I've decided to release a jQuery version.

 

On this blog post, I won't show you how to generate this library because the process is very similar to AngularJS. In case you are interested in generating your own library, please refer to the AngularJS version of this post and also visit this GitHub repository which has the generator of this library.

 

If you want to download the library please visit this GitHub repository. A sample web application using this library is available here.

 

What is Swagger?

 

If you visit the Swagger web site, this is how they define their product: "The goal of Swagger™ is to define a standard, language-agnostic interface to REST APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. When properly defined via Swagger, a consumer can understand and interact with the remote service with a minimal amount of implementation logic. Similar to what interfaces have done for lower-level programming, Swagger removes the guesswork in calling the service.".

 

Please visit this page to find more information about getting started with Swagger.

 

How to access the PI Web API Swagger definition?

 

Just make a GET request against the following url: https://servername/piwebapi/help/specification?pretty=true. You will receive a JSON response back with the Swagger definition.

Remember that you must be using at least PI Web API 2017 in order to have access to the Swagger definition. This url won't work with PI Web API 2016 R2 and older versions of the product. Nevertheless, if you download the library from our GitHub repository, it will be compatible with all PI Web API versions. Just expect to receive some exceptions in case you are using a method that is not implemented on the server side as result of not using the latest PI Web API version.

 

Requirements

 

  • PI Web API 2017 installed within your domain using Kerberos or Basic Authentication.
  • jQuery 1.7.1+

 

Installation

 

  • Download this source code
  • Copy the files from the dist folder to one of your web application folders.

 

Usage

 

  • Refer to the jquery-piwebapi.js or jquery-piwebapi.min.js (make sure it is after loading the jQuery library).

 

Source Code

 

The solution that generates the final library is available on the src folder. You might want to add or edit a method and rebuild the solution in order to generate custom assemblies. The links from both GitHub repositories are below:

 

 

Documentation

 

All classes and methods are described on the DOCUMENTATION.

 

Examples

 

Please check the sample_main.js from this repository. Below there are also code snippets written in JavaScript for you to get started using this library:

 

Set up the instance of the PI Web API top level object.

 

    var piwebapi = new PIWebApi("https://marc-web-sql.marc.net/piwebapi", true); 

If you want to use basic authentication instead of Kerberos, set useKerberos to false and set the username and password accordingly.

    var piwebapi = new PIWebApi("https://marc-web-sql.marc.net/piwebapi", false, "username", "password"); 

 

Get the PI Data Archive WebId

 

    piwebapi.dataServer.getByPath('\\\\MARC-PI2016').then(function (response) { dataServer = response.data; }, function (error) { console.log(error); });

 

Create a new PI Point

 

    var newPoint = new PIWebApiClient.PIPoint(null, null, "SINUSOID_TEST124121115", null, "Test PI Point for jQuery PI Web API Client", "classic", "float32", null, null, null, false); 
    piwebapi.dataServer.createPoint(dataServer.WebId, newPoint).then(function (response) { console.log(response.data); }, function (error) { console.log(error); });       

 

Get PI Point WebId

 

    piwebapi.point.getByPath("\\\\MARC-PI2016\\sinusoid").then(function (response) { 
       var webId = response.data.WebId; },
    function (error) {
       console.log(error);
   });

 

Get recorded values in bulk using the StreamSet/GetRecordedAdHoc

 

    var webIds = [] 
    webIds.push(point1webId);
    webIds.push(point2webId);
    webIds.push(point3webId);
    piwebapi.streamSet.getRecordedAdHoc(webIds, null, "*", null, true, 1000, null, "*-3d", null).then(function (response) {
      console.log(response.data);
    }, function (error) {
      console.log(error);
    });

 

Send values in bulk using the StreamSet/UpdateValuesAdHoc

 

    streamValuesItems = new PIWebApiClient.PIItemsStreamValues() 
    streamValue1 = new PIWebApiClient.PIStreamValues()
    streamValue2 = new PIWebApiClient.PIStreamValues()
    streamValue3 = new PIWebApiClient.PIStreamValues() 
    value1 = new PIWebApiClient.PITimedValue()
    value2 = new PIWebApiClient.PITimedValue()
    value3 = new PIWebApiClient.PITimedValue()
    value4 = new PIWebApiClient.PITimedValue()
    value5 = new PIWebApiClient.PITimedValue()
    value6 = new PIWebApiClient.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.WebId = point1webId
    streamValue2.WebId = point2webId
    streamValue3.WebId = point3webId 
    values1 = []; values1.push(value1)
    values1.push(value2)
    streamValue1.Items = values1 
    values2 = []; values2.push(value3)
    values2.push(value4)
    streamValue2.Items = values2
    values3 = [];
    values3.push(value5)
    values3.push(value6)
    streamValue3.Items = values3
    streamValues = []
    streamValues.push(streamValue1)
    streamValues.push(streamValue2)
    streamValues.push(streamValue3)
    piwebapi.streamSet.updateValuesAdHoc(streamValues, null, null).then(function (response) {
          console.log(response.data);
    }, function (error) { console.log(error); });

 

Developing a web application using jQuery and PI Web API client library for jQuery

 

In order to test this new client library, let's take the example from the blog post. First we need to edit the pi_data_result.html:

 

    <script src="Scripts/jquery-3.1.1.js"></script>
    <script src="Scripts/pi_data_result.js"></script>
    <script src="Scripts/jquery-piwebapi.js"></script>

 

You might need to install jQuery NuGet package using the command on the Package Manager: Install-Package jQuery

 

The original pi_data_result.js is:

 

function GetQueryStringParams(sParam) {
    var sPageURL = window.location.search.substring(1);
    var sURLVariables = sPageURL.split('&');
    for (var i = 0; i < sURLVariables.length; i++) {
        var sParameterName = sURLVariables[i].split('=');
        if (sParameterName[0] == sParam) {
            return sParameterName[1];
        }
    }
}


function AppendErrorToTable(DataObj, TableToAdd) {
    var ErrorMsgs = DataObj;
    $('<tr/>', {
        'text': ErrorMsgs
    }).appendTo(TableToAdd);
}


function StartRetrievalMethod(PerformRequest, RetrievalMethodName, TableToAdd, RetrievalMethodClass, RetrievalMethodData) {


    if (PerformRequest == "yes") {
        try {
            for (var i = 0; i < RetrievalMethodData["Items"].length; i++) {
                $('<tr/>', {
                    'id': RetrievalMethodName + 'Tr' + i,
                }).appendTo(TableToAdd);
                $('<td/>', {
                    'text': RetrievalMethodData["Items"][i].Value
                }).appendTo('#' + RetrievalMethodName + 'Tr' + i);
                $('<td/>', {
                    'text': RetrievalMethodData["Items"][i].Timestamp
                }).appendTo('#' + RetrievalMethodName + 'Tr' + i);
            }
        }
        catch (e) {
            if (RetrievalMethodData["Value"] != undefined) {
                $('<tr/>', {
                    'id': RetrievalMethodName + 'Tr',
                }).appendTo(TableToAdd);
                $('<td/>', {
                    'text': RetrievalMethodData["Value"]
                }).appendTo('#' + RetrievalMethodName + 'Tr');
                $('<td/>', {
                    'text': RetrievalMethodData["Timestamp"]
                }).appendTo('#' + RetrievalMethodName + 'Tr');
            }
            else {
                AppendErrorToTable(RetrievalMethodData, TableToAdd);
            }
        }


        $(RetrievalMethodClass).css("visibility", "visible")
    }
    else {
        $(RetrievalMethodClass).css("visibility", "hidden")
    }




}


var piServerName = "";
var piPointName = ""
var startTime = ""
var endTime = ""
var interval = ""


$(document).ready(function () {
    piServerName = GetQueryStringParams('piServerName');
    piPointName = GetQueryStringParams('piPointName');
    startTime = GetQueryStringParams('startTime');
    endTime = GetQueryStringParams('endTime');
    interval = GetQueryStringParams('interval');
    var getsnap = GetQueryStringParams('getsnap');
    var getrec = GetQueryStringParams('getrec');
    var getint = GetQueryStringParams('getint');
    $("#PIServerNameValue").text(piServerName);
    $("#PIPointNameValue").text(piPointName);
    $("#StartTimeValue").text(startTime);
    $("#EndTimeValue").text(endTime);
    $("#IntervalValue").text(interval);


    piwebapi.SetBaseServiceUrl("https://devdata.osisoft.com/piwebapi");
    piwebapi.ValidPIServerName(piServerName).then(function () {
        $("#PIServerExistValue").text("true");
    }, function (error) {
        $("#PIServerExistValue").text("false");
    });
    piwebapi.ValidPIPointName(piServerName, piPointName).then(function (piPointData) { 
        $("#PIPointExistValue").text("true");
        piwebapi.GetSnapshotValue(piPointData).then(function (data) { 
            StartRetrievalMethod(getsnap, 'Snapshot', 'table.snapshot', ".snapshot", data) 
        }, function (error) { });
        piwebapi.GetRecordedValues(piPointData, startTime, endTime).then(function (data) { 
            StartRetrievalMethod(getrec, 'Recorded', 'table.recorded', ".recorded", data)
        }, function (error) { });
        piwebapi.GetInterpolatedValues(piPointData, startTime, endTime, interval).then(function (data) { 
            StartRetrievalMethod(getint, 'Interpolated', 'table.interpolated', ".interpolated", data);
        }, function (error) { });
    }, function () {
        $("#PIPointExistValue").text("false");
    });

});

 

 

The new pi_data_result.js looks like:

 


$(document).ready(function () {
    piServerName = GetQueryStringParams('piServerName');
    piPointName = GetQueryStringParams('piPointName');
    startTime = GetQueryStringParams('startTime');
    endTime = GetQueryStringParams('endTime');
    interval = GetQueryStringParams('interval');
    var getsnap = GetQueryStringParams('getsnap');
    var getrec = GetQueryStringParams('getrec');
    var getint = GetQueryStringParams('getint');
    $("#PIServerNameValue").text(piServerName);
    $("#PIPointNameValue").text(piPointName);
    $("#StartTimeValue").text(startTime);
    $("#EndTimeValue").text(endTime);
    $("#IntervalValue").text(interval);
    var piwebapi = new PIWebApi("https://marc-web-sql.marc.net/piwebapi", true);




    piwebapi.dataServer.getByPath('\\\\' + piServerName).then(function (response) {
        $("#PIServerExistValue").text("true");
    }, function (error) {
        $("#PIServerExistValue").text("false");
    });




    piwebapi.point.getByPath('\\\\' + piServerName + '\\' + piPointName, null, null).then(function (response) {
        $("#PIPointExistValue").text("true");


        piwebapi.stream.getValue(response.data.WebId).then(function (response) {
            StartRetrievalMethod(getsnap, 'Snapshot', 'table.snapshot', ".snapshot", response.data) 
        }, function (error) { });
        piwebapi.stream.getRecorded(response.data.WebId, null, null, endTime, null, null, null, null, startTime).then(function (response) {
            StartRetrievalMethod(getrec, 'Recorded', 'table.recorded', ".recorded", response.data) 
        }, function (error) { });
        piwebapi.stream.getInterpolated(response.data.WebId, null, endTime, null, null, interval, null, null, null, null, null, startTime).then(function (response) {
            StartRetrievalMethod(getint, 'Interpolated', 'table.interpolated', ".interpolated", response.data) 
        }, function (error) { });
    }, function () {
        $("#PIPointExistValue").text("false");
    });

});

 

 

Using the library, you don't need to write the piwebapi_wrapper.js. Just use this client library instead and it will work!

 

Conclusion

 

I really think that having PI Web API client libraries available for many different platforms with make our lives much easier. Just download it from this GitHub repository and start developing web apps using jQuery and PI Web API.

Introduction

 

Today we release our first version of the PI Web API client library for Java and Android. This library was generated using the Swagger specification available on PI Web API 2017+.

 

PI JDBC still is our official solution for retrieving PI data to Java environments. Nevertheless, there are a lot of advantages using this open-source project:

 

  • You don't need to know SQL to start coding! You just need to know how to program in Java. Since PI JDBC uses PI OLEDB Provider and PI OLEDB Enterprise under the hood, the developer needs to write SQL queries in order to retrieve/update PI data.
  • There are more methods available in PI Web API than PI JDBC. Besides, that queries against PI AF through PI JDBC and PI OLEDB Enterprise are still read-only in (written in August, 2017).
  • The machines running your Java applications don't need any additional software to be installed. This is not the case for PI JDBC, which needs to be installed on all the machines running the custom Java app.
  • You can develop Android apps with this library. PI JDBC does not allow you to do the same.

 

Requirements

 

  • PI Web API 2017 installed within your domain using Basic Authentication.
  • Building this PI Web API client library requires JDK and Maven to be installed.
  • If you are using the .NET Core version of this library, you should have .NET Core 1.1 installed on your machine.

 

Installation

 

To install the API client library to your local Maven repository, simply execute on the project folder:

 

mvn install

 

To deploy it to a remote Maven repository instead, configure the settings of the repository and execute:

 

mvn deploy

 

Refer to the official documentation for more information.

 

Usage

 

After building this client library using Maven, create a new Java project using your favorite IDE. Using Maven or Gradle, add the library according to the instructions below:

 

Maven users

 

Add this dependency to your project's POM:

 

<dependency>
  <groupId>com.osisoft.pidevclub</groupId>
  <artifactId>piwebapi</artifactId>
  <version>1.0.0</version>
</dependency>

 

Gradle users

 

Add this dependency to your project's build file:

compile 'com.osisoft.pidevclub:piwebapi:1.0.0'

 

Others

 

At first generate the JAR by executing:

mvn package 

Then manually install the following JARs:

  • target/piwebapi-1.0.0.jar
  • target/lib/*.jar

 

 

Source Code

 

The InterlliJ project that generates the final library is available on the src folder. You might want to add or edit a method and rebuild the solution in order to generate custom assemblies. The links from the GitHub repository is below:

 

 

Documentation

 

All classes and methods are described on the DOCUMENTATION.

 

Examples

 

Please check the PIWebApiTests.java from the Java test module of this repository. Below there are also code snippets written in Java for you to get started using this library:

 

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

 

PIWebApiClient client = new PIWebApiClient("https://marc-web-sql.marc.net/piwebapi", username, password, false, true);  

 

 

This library is only compatible with PI Web API Basic Authentication. As a result, you must provide the username and password.

 

Get the PI Data Archive WebId

 

PIDataServer dataServer = client.getDataServer().getByPath("\\\\MARC-PI2016", null);

 

Create a new PI Point

 

PIDataServer dataServer = client.getDataServer().getByPath("\\\\MARC-PI2016, null);
PIPoint newPoint = new PIPoint();
newPoint.setName("SINUSOID_TEST5");
newPoint.setDescriptor("Test PI Point for Java PI Web API Client");
newPoint.setPointClass("classic");
newPoint.setPointType("float32");
newPoint.setFuture(false);
ApiResponse<Void> res =  client.getDataServer().createPointWithHttpInfo(dataServer.getWebId(),newPoint);     

 

Get PI Points WebIds

 

PIPoint point1 = client.getPoint().getByPath("\\\\JUPITER001\\sinusoid", null);
PIPoint point2 = client.getPoint().getByPath("\\\\JUPITER001\\sinusoidu", null);
PIPoint point3 = client.getPoint().getByPath("\\\\JUPITER001\\cdt158", null);

 

Get recorded values in bulk using the StreamSet/GetRecordedAdHoc

 

List<String> webIds = new ArrayList<String>();
webIds.add(point1.getWebId());
webIds.add(point2.getWebId());
webIds.add(point3.getWebId());
PIItemsStreamValues piItemsStreamValues = client.getStreamSet().getRecordedAdHoc(webIds,null, "*", null, true, 1000, null, "*-3d",null);

 

Send values in bulk using the StreamSet/UpdateValuesAdHoc

 

PIItemsStreamValues streamValuesItems = new PIItemsStreamValues();
PIStreamValues streamValue1 = new PIStreamValues();
PIStreamValues streamValue2 = new PIStreamValues();
PIStreamValues streamValue3 = new PIStreamValues();
PITimedValue value1 = new PITimedValue();
PITimedValue value2 = new PITimedValue();
PITimedValue value3 = new PITimedValue();
PITimedValue value4 = new PITimedValue();
PITimedValue value5 = new PITimedValue();
PITimedValue value6 = new PITimedValue();
value1.setValue(2);
value1.setTimestamp("*-1d");
value2.setValue(3);
value2.setTimestamp("*-2d");
value3.setValue(4);
value3.setTimestamp("*-1d");
value4.setValue(5);
value4.setTimestamp("*-2d");
value5.setValue(6);
value5.setTimestamp("*-1d");
value6.setValue(7);
value6.setTimestamp("*-2d");
streamValue1.setWebId(point1.getWebId());
streamValue2.setWebId(point2.getWebId());
streamValue3.setWebId(point3.getWebId());


List<PITimedValue> values1 = new ArrayList<PITimedValue>();
values1.add(value1);
values1.add(value2);
streamValue1.setItems(values1);


List<PITimedValue> values2 = new ArrayList<PITimedValue>();
values2.add(value3);
values2.add(value4);
streamValue2.setItems(values2);


List<PITimedValue> values3 = new ArrayList<PITimedValue>();
values3.add(value5);
values3.add(value6);
streamValue3.setItems(values3);


List<PIStreamValues> streamValues = new ArrayList<PIStreamValues>();
streamValues.add(streamValue1);
streamValues.add(streamValue2);
streamValues.add(streamValue3);
ApiResponse<PIItemsItemsSubstatus> res = client.getStreamSet().updateValuesAdHocWithHttpInfo(streamValues, null,null);

 

Get element and its attributes given an AF Element path

 

PIElement myElement = client.getElement().getByPath("\\\\MARC-PI2016\\CrossPlatformLab\\marc.adm", null);
PIItemsAttribute attributes = client.getElement().getAttributes(myElement.getWebId(), null, 1000, null, false, null, null,null,null,null,0,null,null);

 

Final Remarks

 

As you could realize by looking at the example, it is pretty easy to use this client library on your Java apps. Please provide your feedback by posting your comments below!

·         You don't need to know SQL to start coding! You just need to know how to program in Java. Since PI JDBC uses PI OLEDB Provider and PI OLEDB Enterprise under the hood, the developer needs to write SQL queries in order to retrieve/update PI data.

·         There are more methods available in PI Web API than PI JDBC. Besides, that queries against PI AF through PI JDBC and PI OLEDB Enterprise are still read-only in (written in August, 2017).

·         The machines running your Java applications don't need any additional software to be installed. This is not the case for PI JDBC, which needs to be installed on all the machines running the custom Java app.

·         You can develop Android apps with this library. PI JDBC does not allow you to do the same.

·         Although it was not tested, the performance should be better for calls in bulk. PI Web API client library allows you to retrieve and send data in bulk. This feature is not available on PI JDBC yet.

Introduction

 

Today we release our first version of the PI Web API client libraries for .NET Framework and .NET Core. Those libraries were generated using the Swagger specification available on PI Web API 2017+.

 

Although PI AF SDK still remains the best choice in case performance is a must, there are a lot of scenarios where it makes sense to develop your .NET applications using with those libraries:

 

  • You cannot install PI AF SDK on the machine which would run your application.
  • You are developing a web application using Azure Web Apps, which doesn't allow you to remote in and install OSIsoft products.
  • Your PI System is hosted in the cloud. Therefore, it makes to expose the data through a public endpoint of PI Web API.
  • You want to create applications that are compatible with Linux and macOS.
  • You want to use the most modern technology of the market.

 

The first two reasons of the list above applies when you are developing custom .NET Framework apps.  The last 3 items from this list applies for .NET Core. But what is .NET Core exactly?

 

What is .NET Core?

 

According to the .NET blog, .NET Core is a cross-platform, open source, and modular .NET platform for creating modern web apps, microservices, libraries and console applications. Because it is cross-platform, you can run your .NET Core apps on Windows, Linux and macOS. Users can use the command-line tools to manage their project.

 

.NET Core is composed by 3 parts: .NET runtime, a set of framework libraries and a set of SDK tools. This new technology can be need as a cross-platform version of the .NET Framework.

 

.NET Core and PI Developer Technologies

 

.NET Core is not compatible with .NET Framework libraries like PI AF SDK. OSIsoft's product management and development teams have ongoing investigations into creating a PI AF SDK version compatible with .NET Core, but at this point there is no timeline for release. Although the PI AF SDK is faster than PI Web API, the performance of our RESTful web service is sufficient for many use cases.  The PI Web API team is committed to improving performance and adding features to each new release.

 

 

If you are developing a long running service or an application that retrieves large amounts of PI data, our suggestion is to develop on top of PI AF SDK and .NET Framework. If this is not the case, using this new client library for developing .NET Core apps should suffice your needs.

 

 

Requirements

 

  • PI Web API 2017 installed within your domain using Kerberos or Basic Authentication.
  • If you are using the .NET Framework version of this library, you should have .NET Framework 4.5 installed on your machine.
  • If you are using the .NET Core version of this library, you should have .NET Core 1.1 installed on your machine.

 

Installation

 

  • Download this source code
  • Create a new folder under %PIHOME% named WebAPIClient, if it doesn't exist.
  • Create a new folder under WebAPIClient named DotNetCore, if it doesn't exist.
  • Copy the unique file from the dist folder to %PIHOME%\WebAPIClient\DotNetCore (.NET Core) or %PIHOME%\WebAPIClient\DotNetCore (.NET Framework).

 

Usage

 

.NET Framework

 

Create a new .NET Framework project (Console Application for instance). On the Solution Explorer, right click on Dependencies and then "Add Reference...". Click on the Browse button and navigate to the %PIHOME%\WebAPIClient\DotNet folder. Finally, add the OSIsoft.PIDevClub.PIWebApiClient.dll to your VS project.

 

.NET Core

 

Create a new .NET Core project (Console Application for instance). Open the Package Manager Console and run the following command to add this library to your .NET Core project.:

 

Install-Package OSIsoft.PIDevClub.PIWebApiClient -Source %PIHOME%\WebAPIClient\DotNetCore

 

Source Code

 

The Visual Studio solution that generates the final library is available on the src folder. You might want to add or edit a method and rebuild the solution in order to generate custom assemblies. The links from both GitHub repositories are below:

 

 

Documentation

 

Once the library was added to your project, you can start writing code. The beauty of this library is that you don't need to know about writing code to make HTTP requests. The library will do that under the hood. All actions and methods from all the controllers of PI Web API 2017 are available on this client library. You just have to create the PI Web API top level object and access its properties. Each property maps a PI Web API controller which has methods which maps the actions from the RESTful web service. Therefore if you want to access the GetByPath action from the Point controller just call piwebapi.Point.GetByPath().

 

All classes and methods are described on here. Even interally both libraries are different, they have the same classes and methods from the user perspective.

 

Examples

 

Please check the Program.cs from the LibraryTest project from the Visual Studio solution of this repository. Below there are also code snippets written in C# for you to get started using this library:

 

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

 

     PIWebApiClient client = new PIWebApiClient("https://marc-web-sql.marc.net/piwebapi", true);  

If you want to use basic authentication instead of Kerberos, set useKerberos to false and set the username and password accordingly.

 

Get the PI Data Archive WebId

 

    PIDataServer dataServer = client.DataServer.GetByPath("\\\\MARC-PI2016");

 

Create a new PI Point

 

    PIPoint newPIPoint = new PIPoint(); newPIPoint.Name = "MyNewPIPoint" newPIPoint.Descriptor = "Point created for wrapper test" newPIPoint.PointClass = "classic" newPIPoint.PointType = "Float32" ApiResponseObject response = client.dataServer.CreatePointWithHttpInfo(dataServer.webId, newPIPoint)

 

Get PI Points WebIds

 

    PIPoint point1 = client.Point.GetByPath("\\\\marc-pi2016\\sinusoid"); PIPoint point2 = client.Point.GetByPath("\\\\marc-pi2016\\sinusoidu"); PIPoint point3 = client.Point.GetByPath("\\\\marc-pi2016\\cdt158");

 

Get recorded values in bulk using the StreamSet/GetRecordedAdHoc

 

    List<string> webIds = new List<string>() { point1.WebId, point2.WebId, point3.WebId }; PIItemsStreamValues piItemsStreamValues = client.StreamSet.GetRecordedAdHoc(webIds, startTime: "*-3d", endTime: "*");

 

Send values in bulk using the StreamSet/UpdateValuesAdHoc

 

     var streamValuesItems = new PIItemsStreamValues(); var streamValue1 = new PIStreamValues(); var streamValue2 = new PIStreamValues(); var streamValue3 = new PIStreamValues(); var value1 = new PITimedValue(); var value2 = new PITimedValue(); var value3 = new PITimedValue(); var value4 = new PITimedValue(); var value5 = new PITimedValue(); var value6 = new 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.WebId = point1.WebId; streamValue1.Items = new List<PITimedValue>(); streamValue1.Items.Add(value1); streamValue1.Items.Add(value2); streamValue2.WebId = point2.WebId; streamValue2.Items = new List<PITimedValue>(); streamValue2.Items.Add(value3); streamValue2.Items.Add(value4); streamValue3.WebId = point2.WebId; streamValue3.Items = new List<PITimedValue>(); streamValue3.Items.Add(value5); streamValue3.Items.Add(value6); ApiResponse<PIItemsItemsSubstatus> response2 = client.StreamSet.UpdateValuesAdHocWithHttpInfo(new List<PIStreamValues>() { streamValue1, streamValue2, streamValue3 });

 

Get element and its attributes given an AF Element path

 

     PIElement myElement = client.Element.GetByPath("\\\\MARC-PI2016\\CrossPlatformLab\\marc.adm"); PIItemsAttribute attributes = client.Element.GetAttributes(myElement.WebId, null, 1000, null, false);

 

Get current value given an AF Attribute path

 

     PIAttribute attribute = client.Attribute.GetByPath(string.Format("{0}|{1}", "\\\\MARC-PI2016\\CrossPlatformLab\\marc.adm", attributes.Items[0].Name)); PITimedValue value = client.Stream.GetEnd(attribute.WebId);

 

Get Event Frames given an AF Database path

 

    PIAssetDatabase db = client.AssetData.GetByPath(path); PIItemsEventFrame efs = client.AssetData.GetEventFrames(db.WebId, referencedElementNameFilter: "myElement", referencedElementTemplateName:

 

Final Remarks

 

As you could realize by looking at the example, it is pretty easy to use this client library on your apps. Please provide your feedback by posting your comments below!

Introduction

 

Although it is considered an old technology, there are still many developers who use VBA to integrate their PI ProcessBook displays or Excel spreadsheets with the PI System. Since our most performant PI Developer Technology is PI AF SDK, which is a native .NET Framework, it cannot be used within the VBA environment. One option is to use PI SDK in VBA in order to communicate with the PI Data Archive. Nevertheless, if those developers want to work with elements, attributes or event frames, there wasn't any really good alternative.

 

Today we are releasing the first version of the PI Web API Wrapper for VBA, which is a client RESTful web service for PI Web API 2017. With this library, almost all methods available on PI Web API 2017 can be called through this library. This means you can get any element description, retrieve its attributes, search for event frames, create new PI Points, sends and get data in bulk within the VBA context.

 

 

It is always good to remember that PI Vision is the most suitable technology for any new development projects. This wrapper should be used only when PI Vision cannot be used for any reason.

 

Requirements

 

  • PI Web API 2017 installed within your domain using Kerberos or Basic Authentication.
  • PI ProcessBook 2012 SP1+
  • .NET Framework 3.5

 

Installation

 

  • Download the latest release from our GitHub repository
  • Create a new folder under %PIHOME% named WebAPIClient, if it doesn't exist.
  • Create a new folder under WebAPIClient named VBA, if it doesn't exist.
  • Copy all files from the dist folder to %PIHOME%\WebAPIClient\VBA.
  • Run as Administrator the reg.bat located on %PIHOME%\WebAPIClient\VBA in order to register the PIWebApiWrapper assmebly.

 

Usage

 

Create or edit a PI ProcessBook display. Press ALT+F11 to open Visual Basic for Applications. On the menu, click on Tools --> References. Find PIWebApiWrapper on the list box of the available reference and add it to the VBA project.

 

 

Source Code

 

The Visual Studio solution that generates the final library is available on the src folder. You might want to add or edit a method and rebuild the solution in order to generate custom assemblies.

 

Documentation

 

All classes and methods are described here on GitHub. You can also use the Object Browser from Visual Basic for Application to read the same information. Please refer to the screenshot below:

 

 

Remember

 

As this is a .NET library with COM objects and methods exposed, in order to be able to be consumed within the VBA environment, there are some things to have in mind, especially when comparing with C# development.

  • VBA is not compatible with async methods. Therefore, only sync methods are available in this library.
  • For each PI Web API action/method of each controller, there are two methods on this client library. One returns the response of the HTTP request itself and the other wraps the response on top of ApiResponse class, providing http information, such as status code. Please refer to the Get and GetWithHttpInfo methods on our documentation and you will realize the difference between them by comparing the method signature.
  • The Batch and Channel controllers are not exposed.
  • When working with data transfer objects (models) with an Items property (such as PIItemsElement), do not access or modify this property directly. Use CreateItemsArray(), GetItem(), SetItem() and GetItemsLength() instead.
  • For models that have the Value property, use SetValueWithString(), SetValueWithInt(), SetValueWithDouble() methods to set this property.
  • For the Api methods, all variables whose type are not a string must be defined. If a string variable is optional, define it as an empty string instead of Null.

 

Examples

 

There are two PI ProcessBook displays available on the Samples folder of this repository. In addition, please refer to the following examples to understand how to use this library:

 

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

 

    Dim client As New PIWebApiClient
    Dim connectedToPIWebAPI As Boolean
    connectedToPIWebAPI = client.Connect("https://marc-web-sql.marc.net/piwebapi", True)

 

If you want to use basic authentication instead of Kerberos, set useKerberos to False and set the username and password accordingly. We recommend using Kerberos because it is the safest option. For basic authentication, the password needs to be hardcoded which is not recommended. If using Kerberos authentication is not an option, protect your VBA code with a password.

 

Get the PI Data Archive WebId

 

    Set dataServer = client.dataServer.GetByName(tbPIDataArchiveName.Text)

 

Create a new PI Point

 

    Dim response As ApiResponseObject
    Dim newPIPoint As New PIPoint
    newPIPoint.Name = "MyNewPIPoint"
    newPIPoint.Descriptor = "Point created for wrapper test"
    newPIPoint.PointClass = "classic"
    newPIPoint.PointType = "Float32"
    Set response = client.dataServer.CreatePointWithHttpInfo(dataServer.webId, newPIPoint)

 

Get PI Points WebIds

 

    Set point1 = client.point.GetByPath("\\" + tbPIDataArchiveName.Text + "\" + tbTagName1.Text)
    Set point2 = client.point.GetByPath("\\" + tbPIDataArchiveName.Text + "\" + tbTagName2.Text)
    Set point3 = client.point.GetByPath("\\" + tbPIDataArchiveName.Text + "\" + tbTagName3.Text)

 

Get recorded values in bulk using the StreamSet/GetRecordedAdHoc

 

    webIds = point1.webId + "," + point2.webId + "," + point3.webId
    Set compressedData = client.StreamSet.GetRecordedAdHoc(webIds, True, 1000)

 

Send values in bulk using the StreamSet/UpdateValuesAdHoc

 

 

    Call GetPIPoints
    Dim streamValuesItems As New PIItemsStreamValues
    Dim streamValue1 As New PIStreamValues
    Dim streamValue2 As New PIStreamValues
    Dim streamValue3 As New PIStreamValues
    Dim value1 As New PITimedValue
    Dim value2 As New PITimedValue
    Dim value3 As New PITimedValue
    Dim value4 As New PITimedValue
    Dim value5 As New PITimedValue
    Dim value6 As New PITimedValue


    streamValuesItems.CreateItemsArray (3)
    value1.SetValueWithInt (2)
    value1.Timestamp = "*-1d"
    value2.SetValueWithInt (3)
    value2.Timestamp = "*-2d"
    value3.SetValueWithInt (4)
    value3.Timestamp = "*-1d"
    value4.SetValueWithInt (5)
    value4.Timestamp = "*-2d"
    value5.SetValueWithInt (6)
    value5.Timestamp = "*-1d"
    value6.SetValueWithInt (7)
    value6.Timestamp = "*-2d"


    streamValue1.webId = point1.webId
    streamValue1.CreateItemsArray (2)
    Call streamValue1.SetItem(0, value1)
    Call streamValue1.SetItem(1, value2)
    Call streamValuesItems.SetItem(0, streamValue1)


    streamValue2.webId = point2.webId
    streamValue2.CreateItemsArray (2)
    Call streamValue2.SetItem(0, value3)
    Call streamValue2.SetItem(1, value4)
    Call streamValuesItems.SetItem(1, streamValue2)


    streamValue3.webId = point2.webId
    streamValue3.CreateItemsArray (2)
    Call streamValue3.SetItem(0, value5)
    Call streamValue3.SetItem(1, value6)
    Call streamValuesItems.SetItem(2, streamValue3)

    Dim response As ApiResponsePIItemsItemsSubstatus
    Set response = client.StreamSet.UpdateValuesAdHocWithHttpInfo(streamValuesItems)

 

Get AF Attribute given an AF Element path

 

    Set elem = client.element.GetByPath(ERD.CurrentContext(ThisDisplay))
    ElemDesc.Contents = elem.Description
    Dim attributes As PIItemsAttribute
    Set attributes = client.element.GetAttributes(elem.webId, 1000, False, False, False, 0)

 

Get current value given an AF Attribute path

 

  attributePath = ERD.CurrentContext(ThisDisplay) + "|" + AttrList.Text
    Set attr = client.attribute.GetByPath(attributePath)
    Set timedValue = client.Stream.GetEnd(attr.webId)
    AttrValue.Contents = timedValue.value

 

Get Event Frames given an AF database path

 

Set db = client.AssetDatabase.GetByPath(dbPath)
Set efs = client.AssetDatabase.GetEventFrames(db.webId, False, False, 100, True, 0, "", "*", "", elem.Name, elem.templateName, "", "", "None", "", "",

 

 

Final Remarks

 

This library will be updated for every new release of PI Web API in order to add the methods on this library which were added to the PI Web API release.

 

Please share your comments and thoughts about this new release! Will PI Web API Wrapper be useful in your upcoming projects?

We are excited to present the first LATAM Regional Conference Programming Hackathonce 2017 winners!

 

The theme of this year's Hackathon was Analytics for Smarter Energy Aware Campus. UC Davis, one campus of the University of California, kindly provided 6 months of energy data. Participants were encouraged to create killer applications for UC Davis by leveraging the PI System infrastructure.

 

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

  • PI Server 2017
  • PI Web API 2017
  • PI Vision 2017
  • PI OLEDB Enterprise 2016 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 10 hours, four groups were able to finish their app and present to the judges!

 

Prizes:

1st place: Drone Syma X5sw 2.4ghz 4 Canais Wifi Câmera , vouchers for trainings, one year free subscription to PI Developers Club

2nd place: Vouchers for trainings, one year free subscription to PI Developers Club

 

Without further do, here are the winners!

 

1st place - JLWM Engenharia

Team members: Luan Carlos Amaral Sandes; Joao Teodoro Marinho; Mateus Gabriel Santos; Willy Rodrigo de Araujo.

 

 

IMG_20170606_175253.jpg

 

They have created an app called Predictive Models and Gamification: reducing energy costs. The UC Davis' buildings would receive points if they consume less energy than estimated by the predictive models that the groups has built.

 

The team used the following technologies:

  • PI Vision

 

Here are some screenshots presented by this group!

 

 

2nd place - Connected 4.0

 

Team members:: Guilherme Tavares, Kaio Lima, Pablo Araya, Rômulo Lemes

 

They developed an app named Connected40 the Value of People.  This app is a web application that shows the buildings on a map. When the user clicks on a building, it will display the energy KPIs of the building. They've created a new KPI which is energy per person to compare the efficiency of the building among others.

 

The team used the following technologies:

  • HTML5/JavaScript
  • PI AF SDK
  • Google Maps JavaScript API

 

Here are some screenshots presented by this group!

 

 

Introduction

 

In 2014, I have written a blog post about Developing a PHP application using PI Web API. At that time, it was necessary to build the URL with strings concatenation in order to make HTTP request against PI Web API. With the Swagger specification that comes with PI Web API 2017 release, I was able to generate a PI Web API Client library for PHP. On this blog post, I will rewrite this PHP application in order to use this library instead of writing some lines of code to generate the URLs. Let's see how this work.

 

The source code package of this PHP application with PI Web API client is available on this GitHub repository.

 

Adding the library to the project

 

Download the PHP files from the  PI Web API Client library for PHP GitHub repository, extract the files and copy the lib folder to the root of your PHP application folder.

 

 

Comparing the piwebapi_wrapper.php files

 

Let's compare both piwebapi_wrapper.php files. The first one is from the 2014 blog post and second one is related to this blog post which uses the PI Web API client library.

 

piwebapi_wrapper.php with no client library.

 

<?php
class PIWebAPI
{
  public static function CheckIfPIServerExists($piServerName)
  {
  $base_service_url = "https://cross-platform-lab-uc2017.osisoft.com/piwebapi/";
  $url = $base_service_url . "dataservers";
  $obj = PIWebAPI::GetJSONObject($url);
  foreach($obj->Items as $myServer)
  {
  if(strtolower($myServer->Name)==strtolower($piServerName))
  {
  return(true);
  }
  }
  return (false);
  }

  public static function CheckIfPIPointExists($piServerName, $piPointName)
  {
  $base_service_url = "https://cross-platform-lab-uc2017.osisoft.com/piwebapi/";
  $url = $base_service_url . "points?path=\\\\" . $piServerName . "\\" . $piPointName;
  $obj1 = PIWebAPI::GetJSONObject($url);
  try {
  if(($obj1->Name)!=null)
  {
  return (true);
  }
  return (false);
  }
  catch (Exception $e)
  {

  }
  }

  public static function GetSnapshot($piServerName, $piPointName)
  {
  $base_service_url = "https://cross-platform-lab-uc2017.osisoft.com/piwebapi/";
  $service_url = $base_service_url . "points?path=\\\\" . $piServerName . "\\" . $piPointName;
  $obj_pipoint = PIWebAPI::GetJSONObject($service_url);
  $url = $obj_pipoint->Links->Value;
  $obj_snapshot = PIWebAPI::GetJSONObject($url);
  return ($obj_snapshot);

  }

  public static function GetRecordedValues($piServerName, $piPointName,$startTime,$endTime)
  {
  $base_service_url = "https://cross-platform-lab-uc2017.osisoft.com/piwebapi/";
  $service_url = $base_service_url . "points?path=\\\\" . $piServerName . "\\" . $piPointName;
  $obj_pipoint = PIWebAPI::GetJSONObject($service_url);
  $url = $obj_pipoint->Links->{'RecordedData'} ."?starttime=" . $startTime . "&endtime=" . $endTime;
  $obj_rec = PIWebAPI::GetJSONObject($url);
  return ($obj_rec);

  }

  public static function  GeInterpolatedValues($piServerName, $piPointName,$startTime,$endTime,$interval)
  {
  $base_service_url = "https://cross-platform-lab-uc2017.osisoft.com/piwebapi/";
  $service_url = $base_service_url . "points?path=\\\\" . $piServerName . "\\" . $piPointName;
  $obj_pipoint = PIWebAPI::GetJSONObject($service_url);
  $url = $obj_pipoint->Links->{'InterpolatedData'} ."?starttime=" . $startTime . "&endtime=" . $endTime . "&interval=" . $interval;
  $obj_int = PIWebAPI::GetJSONObject($url);
  return ($obj_int);

  }

  private static function GetJSONObject($url)
  {
  $username = "username";
  $password= "password";
  $ch = curl_init($url);
  curl_setopt($ch, CURLOPT_HEADER, false);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
  curl_setopt($ch, CURLOPT_USERPWD, $username.":".$password); 
  $result = curl_exec($ch);
  $json_o=json_decode($result);
  return ($json_o);
  }
}

 

 

piwebapi_wrapper.php with PHP client library.

 

 

<?php
include_once ( __DIR__ . "\\lib\\PIWebApiLoader.php");
use \PIWebAPI\Client\PIWebApiClient;
class PIWebAPI
{
  public $piwebapi= NULL;
  public function __construct()
  {
  $this->piwebapi = new PIWebApiClient("https://cross-platform-lab-uc2017.osisoft.com/piwebapi","username", "password", "BASIC", FALSE, TRUE);
  }


  public function checkIfPIServerExists($piServerName)
  {

  try 
  {
  $response = $this->piwebapi->dataServer->dataServerGetByNameWithHttpInfo($piServerName);
  if($response[1] == 200)
  {
  return true;
  }
  else {
  return false;
  }
  } 
  catch (Exception $e) {
  return false;
  }


  }

  public function getPIPoint($piServerName, $piPointName)
  {
  try
  {
  $path = "\\\\" . $piServerName . "\\" . $piPointName;
  $response = $this->piwebapi->point->pointGetByPathWithHttpInfo($path);
  if($response[1] == 200)
  {
  return $response;
  }
  else {
  return null;
  }
  }
  catch (Exception $e) {
  return null;
  }
  }

  public function getSnapshot($webId)
  {
  return $this->piwebapi->stream->streamGetEnd($webId);
  }

  public function getRecordedValues($webId, $startTime, $endTime)
  {
  return $this->piwebapi->stream->streamGetRecorded($webId, null, null, $endTime, null, null, null, null, $startTime);
  }

  public function  getInterpolatedValues($webId, $startTime, $endTime, $interval)
  {
  return $this->piwebapi->stream->streamGetInterpolated($webId, null, $endTime, null, null, $interval, null, $startTime);
  }
}

 

The readme.rd from the GitHub repository provides information about how to use this library within a PHP application. Look how easy it is to switch from Basic to Kerberos authentication. Just change the value for $authMethod (which is an input of the PIWebAPIClient constructor) from  "BASIC" to "KERBEROS".

 

 

Updating index.php

 

The objects returned by the methods from the PIWebAPI class are different when compared to the older project. The reason is that the Swagger Code generation will generate classes basic on the specification Json description. As a result, the way you extract the values on the index.php file to render on the tables is a little different:

 

function displaySnapValues($SinusoidSnap) {
  ?>
  <h2>Snapshot Value of Sinusoid</h2>
  <br />
  <table style="width: 20em; border: 1px solid #666;">
  <tr>
  <th>Value</th>
  <th>Timestamp</th>
  </tr>
  <tr>
  <td><?php echo $SinusoidSnap['value'][0]; ?></td>
  <td><?php echo $SinusoidSnap['timestamp']->format('Y-m-d H:i:s'); ?></td>
  </tr>
  </table>
  <br />
  <br />
  <?php
}
function displayRecValues($SinusoidRec) {
  ?>

  <h2>Recorded Values of Sinusoid</h2>
  <br />
  <table style="width: 20em; border: 1px solid #666;">
  <tr>
  <th>Value</th>
  <th>Timestamp</th>
  </tr><?php
  foreach ( $SinusoidRec['items'] as $item) {
  echo "\n<tr>";
  echo "\n\t<td>" . $item['value'][0] . '</td>';
  echo "\n\t<td>" . $item['timestamp']->format('Y-m-d H:i:s') . "</td>";
  echo "\n</tr>";
  }
  ?>

  </table>


  <br />
  <br />
  <?php
}
function displayIntValues($SinusoidInt) {
  ?>
  <h2>Interpolated Values of Sinusoid</h2>
  <br />
  <table style="width: 20em; border: 1px solid #666;">
  <tr>
  <th>Value</th>
  <th>Timestamp</th>
  </tr>
  <?php
  foreach ( $SinusoidInt['items'] as $item) {
  echo "\n<tr>";
  echo "\n\t<td>" . $item['value'][0] . '</td>';
  echo "\n\t<td>" . $item['timestamp']->format('Y-m-d H:i:s') . "</td>";
  echo "\n</tr>";
  }
  ?>
  </table>
  <?php
}

 

Conclusion

 

Although PHP is not my favorite language for web development, I have seen some questions on PI Square about using PHP to retrieve PI data through PI Web API. As a result, I think this library will be very useful for those PHP developers who wants to add value to their application by integrating it with the PI System.

LATAM Regional Conference Programming Hackathon 2017

Clique aqui para fazer seu cadastro!

 

Data: 5 de junho de 2017 das 9h30h às 19h30

Local: OSIsoft Brasil  - Alameda Santos, 1940, 15.o andar - São Paulo, SP, Brasil

 

Participe do primeiro Hackathon de programação na LATAM Regional Conference para aprender, conhecer outros profissionais da sua área e competir por prêmios! Se você é um cientista de dados, administrador, integrador, desenvolvedor ou arquiteto do PI System, você deve participar deste evento desafiador. Nós vamos fornecer para os participantes todas as ferramentas necessárias além de um ambiente pronto para desenvolverem uma aplicação em 10 horas. Vários especialistas estarão presentes para responderem às dúvidas técnicas dos participantes.

 

Você terá a sua disposição uma estrutura de ativos e dados reais. Usando o PI System e nossas tecnologias para desenvolvedores (PI Developer Technologies), você deverá desenvolver uma aplicação utilizando sua criatividade que agregará valor para consumidores e empresas utilizando o poder dos dados! Você trabalhará em um grupo para atingir esse objetivo, utilizando todos os recursos do PI System, incluindo os dados em tempo real, e suas habilidades inovadoras. No final, a sua aplicação será avaliada por um grupo de especialistas. As melhores aplicações serão premiadas, e você e sua equipe serão reconhecidos publicamente pela conquista. Se você tiver alguma dúvida sobre este evento, entre em contato com mloeff@osisoft.com.

 

 

P: Quem deve participar nesse evento? 

R: Se você é um desenvolvedor, arquiteto de sistemas, integrador, cientista de dados ou tecnólogo interessado em aplicações de transformação digital industrial, este evento é para você.

 

 

P: Quais são os principais benefícios de participar nesse evento?

R: Você poderá competir em um evento único para ganhar prêmios e reconhecimento dentro de nossa comunidade de usuários do PI System. Além disso, este evento é uma ótima oportunidade para conhecer e trabalhar junto com outros profissionais de sua área. Finalmente, você aprenderá sobre às novíssimas tecnologias não somente do mercado, mas também do PI System.

 

 

P: O que devo trazer? 

R: Você utilizará o seu próprio laptop durante este evento, portanto, não esqueça de trazê-lo. Forneceremos a infraestrutura de dados e o suporte técnico.

 

 

 

P: Quando e onde ocorrerá o evento? Ele entrará em conflito com qualquer outra atividade do Seminário Regional?

R: Não. O hackathon ocorrerá na segunda-feira, 5 de junho de 2017, das 9h30 às 19h30 no escritório da OSIsoft em São Paulo. Este evento de programação não se sobrepõe a nenhuma outra atividade principal do seminário regional, já que a abertura oficial da conferência será no dia seguinte (terça-feira).

 

 

P: Já me inscrevi no LATAM Regional Conference mas não no Hackathon de Programação. O que devo fazer? 

R: Envie um e-mail para mloeff@osisoft.com

Introduction

 

On the first 3 blog posts (part 1, part 2 and part 3) about developing the Google Maps custom symbol for PI Vision 3 (2016, 2016 R2, 2017 and 2017 R2)., I have shown you how to create a custom symbol showing a Google Map with some markers that represent the updated location from many assets.

 

On this blog post, I will show how to show display on the map historical data by using event frames.

 

You can download the custom symbol in this GitHub repository.

 

Understanding the problem

 

In the past, I have recorded several times the geolocation coordinates when I've walked from my old home to our old OSIsoft office here in São Paulo using an Android app called RunKeeper. After I've stopped recording the activity, the app allowed me to download all routes of activities recorded by downloading a GPX file. By creating a simple console application, I was able to send all the latitude and longitude values to a PI System.

 

On my PI AF Server, I have created a new database called GMapsPart4 with only 1 element called Marcos. This element is derived from an element template called UserTemplate with 2 attribute template called Latitude and Longitude.

 

 

I've also created an event frame for each activity downloaded in the GPX format to make it easier access this kind of information programmatically. There are 3 event frames on this database. The primary referenced element in all of them is the Marcos element. Each attribute from the element map the attribute from the Marcos element as shown below. An event frame template was created since all EFs follow the same pattern.

 

 

Below there is a screenshot taken using the final version of the custom symbol developed on this blog post (part 4):

 

 

 

As you can see, the Event Frames mapped with the Marcos elements are below. Once the user clicks on any event frame, the symbol retrieves the historical geolocation data within the time range defined by the event frame. Using Google Maps API, we have added a path on the map and a marker. The marker moves when the user changes the slider's value, which is above the map.

 

This will only happen if the user selects the Historical Mode on the configuration options as shown below:

 

 

 

Adding the PI Web API Client library for AngularJS to the PI Vision infrastructure

 

PI Vision does not allow you to retrieve event frames or recorded values through its native extensibility model. Although for the majority of the symbols, this is not a problem, there are some use cases which require the custom symbol to interact with PI Web API in order to retrieve additional information. Please refer to my previous blog post in order to make the piwebapi Angular service available on your PI Vision infrastructure, otherwise, this symbol won't work on your PI Vision 2017 (and 2016 R2).

 

 

RangeSlider

 

The slider above the map is an objected created with the RangeSlider.js JavaScript library. Please download the library from their official site and paste it on the \symbols\ext\libraries folder. I am using version 2.3.

 

 

Updating the sym-gmaps-template.html

 

Following the examples provided on the official site from RangeSlider.js library, I've added the horizontal slider on the top of the symbol. Below the map, I have added a div node in order to list the event frames retrieved.

 

 

<div ng-if="config.HistoricalMode" style="width:100%;height:50px;">
    <input type="range"
           min="0"
           max="{{rangeMax}}"
           step="1"
           value="0"
           data-orientation="horizontal">
</div>
<div id="container" style="width:100%;height:calc(100% - 150px);">


</div>


<div ng-if="config.HistoricalMode" class="activities-pane">
    <div ng-repeat="activity in activitiesList" ng-class="activity.WebId == selectedActivity.WebId ? 'activity-non-selected' : 'activity-selected'" ng-click="updateActivity(activity)">
        <p>
            {{activity.Name}}
        </p>
    </div>
</div>


<style>
    .activities-pane {
        background-color: white;
        padding: 1px;
        height: 100px;
        overflow-y: auto;
    }
        .activities-pane div {
            height: 31px;
            border: azure;
            margin: 4px;
            padding-top: 8px;
        }
        .activities-pane p {
            color: white;
        }
    .activity-selected {
        background: brown;
    }
    .activity-non-selected {
        background: blue;
    }
</style>

 

Finally, to make things easier, I've added a new style within the HTML itself although the best practice would be to have a new CSS file just for this purpose.

 

Updating the sym-gmaps.js

 

I won't describe all the steps required to update the sym-gmaps.js from part 3 to part 4. I will focus on the main logic for you understand what is going on under the hood.

 

The first thing is to inject the piwebapi service and add the HistoricalMode property of the main definition:

 

       inject: ['piwebapi'],
        getDefaultConfig: function () {
            return {
                DataShape: 'Table',
                Height: 600,
                Width: 400,
                MarkerColor: 'rgb(255,0,0)',
                LatName: 'Latitude',
                LngName: 'Longitude',
                HistoricalMode: false,
                OpenInfoBox: true,

 

 

If the HistoricalMode is false, the symbol would work very similar to the symbol developed on part 3. The new features can be viewed if HistoricalMode is true.

 

On the init function, call the piwebapi functions to initialize the service as described on the other blog post:

 

   symbolVis.prototype.init = function init(scope, elem, piwebapi) {
piwebapi.ConfigureInstance("https://marc-web-sql.marc.net/piwebapi", true); 

 

We need to use the extensibility model to extract the AF database path and the root element name.

 

 

            if ((scope.elementName == undefined) && (scope.lastDataWithPath.Rows[0].Path.substring(0, 3) == "af:")) {
                var elementPath = (data.Rows[0].Path.split("|")[0]).substring(3);
                var stringData = elementPath.substring(2).split("\\");
                scope.databasePath = "\\\\" + stringData[0] + "\\" + stringData[1];
                scope.elementName = stringData[2];
            }

 

 

The AF database path is used to get the AF database WebId, which wil the element name, both are used as inputs for the GetEventFrames method from the AssetDatabase controller.

 

 if (scope.activitiesList == undefined) {
                        piwebapi.assetDatabase.getByPath(scope.databasePath, null, null).then(function (response) {
                            var webId = response.data.WebId;
                            piwebapi.assetDatabase.getEventFrames(webId, null, null, "*", null, 100, null, scope.elementName, "UserTemplate", true, null, null, null, null, null, null, "*-900d").then(function (response) {
                                scope.activitiesList = response.data.Items;
                                scope.selectedActivity = response.data.Items[0];
                                scope.updateActivity(scope.selectedActivity);
                            });
                        });
                    }

 

The scope.updateActivity method updates the UI for a given activity (which is an Event Frame) following the steps below:

 

  • Clean markers and paths from the map.
  • Get the attributes WebId from the user element.
  • Get interpolated values in bulk by using the GetInterpolatedAdHoc method from the StreamSet controller.
  • Polish the values to be consumed by the Google Maps API.
  • Create the path with the retrieved PI Values and add it to the Google Map.
  • Update the color of the marker if needed.
  • Instantiate the rangeSlider object using the JavaScript library methods.

 

   scope.updateActivity = function (activity) {
            if ((scope.marker != null) && (scope.marker != undefined)) {
                scope.marker.setMap(null);


            }
            if ((scope.routePath != null) && (scope.routePath != undefined)) {
                scope.routePath.setMap(null);
            }


            scope.selectedActivity = activity;
            scope.loadingGeolocation = true;
            var elementWebId = scope.selectedActivity.RefElementWebIds[0];
            piwebapi.element.getAttributes(elementWebId).then(function (response) {
                scope.attributes = response.data.Items;
                var webIds = new Array(scope.attributes.length);
                for (var i = 0; i < scope.attributes.length; i++) {
                    webIds[i] = scope.attributes[i].WebId;
                }


                piwebapi.streamSet.getInterpolatedAdHoc(webIds, activity.EndTime, null, true, "30s", null, activity.StartTime).then(function (response) {
                    for (var i = 0; i < response.data.Items.length; i++) {
                        var currentItem = response.data.Items[i];
                        if (currentItem.Name == scope.config.LatName) {
                            scope.latitudeTrack = currentItem.Items;
                        }
                        if (currentItem.Name == scope.config.LngName) {
                            scope.longitudeTrack = currentItem.Items;
                        }
                    }


                    var bounds = new google.maps.LatLngBounds();
                    var routeCoordinates = [];
                    for (var i = 0; i < scope.latitudeTrack.length; i++) {
                        if ((scope.latitudeTrack[i].Good == true) && (scope.longitudeTrack[i].Good == true)) {
                            var pos = { lat: scope.latitudeTrack[i].Value, lng: scope.longitudeTrack[i].Value };
                            routeCoordinates.push(pos);
                            var point = new google.maps.LatLng(pos.lat, pos.lng);
                            bounds.extend(point);
                        }
                    }
                    scope.rangeMax = routeCoordinates.length;


                    scope.routePath = new google.maps.Polyline({
                        path: routeCoordinates,
                        geodesic: true,
                        strokeColor: '#FF0000',
                        strokeOpacity: 1.0,
                        strokeWeight: 2
                    });


                    scope.routePath.setMap(scope.map);
                    scope.map.fitBounds(bounds);


                    scope.marker = new google.maps.Marker({
                        position: routeCoordinates[0],
                        map: scope.map
                    });


                    updateMarkerColor(scope.marker, scope.config);


                    $('input[type="range"]').rangeslider({
                        polyfill: false,
                        onSlide: function (position, value) {
                            x = Math.round(position);
                            var geoPosition = routeCoordinates[x];
                            scope.marker.setPosition(geoPosition);
                        }
                    });
                });
            });
        };

 

 

 

Conclusions

 

The first PI Coresight which has the extensiblity model is the 2016 version. It allows you to develop custom symbols for PI Coresight. The PI Coresight extensibility model keeps sending live data to be consumed by the custom symbol. Nevertheless, there are lot of use cases and interest by the PI DevClub community to integrate the custom symbol with PI Web API. This blog post provides information about how to achieve this goal in order to create richer and more valueable custom symbols for your enterprise and/or your customers.

Introduction

 

Since the release of PI Vision extensibility, I have seen a lot of questions on PI Developers Club about how to make HTTP calls against PI Web API within the custom symbol methods.

 

On my last blog post, I have shown step by step about how to generate a PI Web API Client library for AngularJS using Swagger codegen. This library is available for download on the dist folder from this GitHub repository. What I do think it would be very interesting and useful for our community is to use this library when developing custom PI Vision symbols. Who wouldn't want to use this feature?

 

Disclaimer

 

Before we start describing the procedures, it is good to remind that you that we will change PI Vision source JavaScript source code. As a result, there are a lot of risks involved, such as:

  • If the changes you've done were not well done, your PI Vision might not load anymore. In this case, you might need to repair the installation to replace all JavaScript files.
  • After a PI Vision upgrade,  all your changes will be undone. You will have to do the procedure again.
  • I strongly recommend testing on a development environment first.
  • Although I don't expect major issues, we haven't tested the problems when using this library within PI Vision.

 

All in all, there are risks involved. Just consider them before following the procedure.

 

UPDATE: A new way was published in order NOT to change the PI Vision 3 source. Click here to read a better approach.

 

 

Making the piwebapi service available on PI Vision

 

Yes, I am writing this procedure only for PI Vision 2016 R2. It should also work on PI Vision 2016 but I haven't tested. If you do test, please write a comment below!

 

Here are the steps:

 

1 - Open the browser and go to this GitHub repository. Download the source code package to a zip file. Within this file, copy the angular-piwebapi-kerberos.min.js (or piwebapi-kerberos.js) file to %PIHOME64%\Coresight\Scripts\app\editor folder (2016 R2) or %PIHOME64%\PIVision\Scripts\app\editor folder (2017).

2 - Edit the Index.html file located on the %PIHOME64%\Coresight\Views\Home folder (2016 R2) or %PIHOME64%\PIVision\Views\Home folder (2017) by adding a reference to angular-piwebapi-kerberos.min.js just below "@Scripts.Render("~/bundles/libraries/jstz")". Please refer to the code below:

 


    @Scripts.Render("~/bundles/libraries/jquery")    
    @Scripts.Render("~/bundles/jqueryui")    
    @Scripts.Render("~/bundles/jqueryui/layout")
    @Scripts.Render("~/bundles/jquery-ui-patch")
    @Scripts.Render("~/bundles/libraries/hammer")
    @Scripts.Render("~/bundles/libraries/angular")
    @Scripts.Render("~/bundles/libraries/angular-gridster")
    @Scripts.Render("~/bundles/libraries/jstz")


  <script src="/PIVision/Scripts/app/editor/angular-piwebapi-kerberos.min.js" /></script>


    @Scripts.Render("~/bundles/kendo-patch")

 

Use src="/Coresight/Scripts/..." instead if you are using 2016 R2 or older versions.

 

3 - Edit the coresight.app.js file (2016 R2) or PIVisualization.app.js (2017) located on the %PIHOME64%\PIVision\Scripts\app\editor (%PIHOME64%\Coresight\Scripts\app\editor for 2016 R2) by adding ngPIWebApi to the depedencies module list from PI Vision. Please refer to the code below:

 

    angular.module(APPNAME, ['ngAnimate', 'Chronicle', 'osi.PiDialog', 'osi.PiToast', 'coresight.routing', 'kendo.directives', 'gridster', 'ngPIWebApi'])
        .config([
            '$animateProvider',
            '$compileProvider',
            '$httpProvider',
            config])

 

Do not forget to save both files.

 

That is all! Easy, right?

 

 

Creating a custom symbol using the piwebapi service

 

Now that our PI Vision is now able to inject our piwebapi service, which is a PI Web API client library using AngularJS, let's create a custom symbol that will make an HTTP request againt the main PI Web API endpoint and display the JSON on the screen.

 

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

 

 

For 2017:

 

(function (PV) {
    function symbolVis() { }
    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 (%PIHOME64%\Coresight\Scripts\app\editor\symbols\ext for 2016 R2) 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

 

The custom symbol developed in this blog post is very simple. Nevertheless, its main purpose is to demonstrate that after making the changes in order to add the PI Web API client library for AngularJS to PI Vision, it is possible to inject the piwebapi service. As a result, it gets easier to make HTTP requests against PI Web API using this client library.

 

Finally, please write comments about this project and share your thoughts!

Introduction

 

The upcoming PI Web API 2017 release will come with lots of great new features. One of the them is the Swagger specification which is a JSON string that allows you to generate client-side libraries in many different programming languages including Java, C#, AngularJS etc...

On this blog post, I will show you how to have access to the PI Web API Swagger JSON specification definition and generate a PI Web API library to be used with any web site developed using AngularJS. If you want only to download the library please visit this GitHub repository. A sample web application using this library is available here.

 

What is Swagger?

 

If you visit the Swagger web site, this is how they define their product: "The goal of Swagger™ is to define a standard, language-agnostic interface to REST APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. When properly defined via Swagger, a consumer can understand and interact with the remote service with a minimal amount of implementation logic. Similar to what interfaces have done for lower-level programming, Swagger removes the guesswork in calling the service.".

 

Please visit this page to find more information about getting started with Swagger.

 

How to access the PI Web API Swagger definition?

 

Just make a GET request against the following url: https://servername/piwebapi/help/specification?pretty=true. You will receive a JSON response back with the Swagger definition.

Remember that you must be using at least PI Web API 2017 in order to have access to the Swagger definition. This url won't work with PI Web API 2016 R2 and older versions of the product. Nevertheless, if you download the library from our GitHub repository, it will be compatible with all PI Web API versions. Just expect to receive some exceptions in case you are using a method that is not implemented on the server side as result of not using the latest PI Web API version.

 

How to generate client libraries using the PI Web API Swagger definition?

 

You can download the Swagger Code Generator directly from this GitHub repository. This application allows generation of API client libraries automatically with the OpenAPI specification. If you visit their repository, you will see that the following languages are supported:

 

  • API clients: ActionScript, Bash, C# (.net 2.0, 4.0 or later), C++ (cpprest, Qt5, Tizen), Clojure, Dart, Elixir, Go, Groovy, Haskell, Java (Jersey1.x, Jersey2.x, OkHttp, Retrofit1.x, Retrofit2.x, Feign), Node.js (ES5, ES6, AngularJS with Google Closure Compiler annotations) Objective-C, Perl, PHP, Python, Ruby, Scala, Swift (2.x, 3.x), Typescript (Angular1.x, Angular2.x, Fetch, jQuery, Node)

 

Read the readme.md file in order to build from source with Java and Apachen Maven.

 

The next step is to run swagger-codegen using JAVA and the Command Prompt:

 

java -jar swagger-codegen/modules/swagger-codegen-cli/target/swagger-codegen-cli.jar generate -i https://servername/piwebapi/help/specification?pretty=true -l typescript-angular -o /PIWebAPIClient/swagger-project/PI-Web-API-Client-library-for-AngularJS

 

The TypeScript AngularJS library will be available under the PIWebAPIClient\swagger-project\PI-Web-API-Client-library-for-AngularJS folder.

 

Since I couldn't control very well how the library is going to be generated with the Swagger Generator, I have decided to create my own generator which is available here. I have described how to generate a client library with the Swagger code generator so you can learn how to generate libraries for many different languages and platforms.

 

 

Compiling the TypeScript libraries to generate JavaScript libraries with VS Code

 

Create a new folder under the PI-Web-API-Client-library-for-AngularJS named src and move the API folder to this new directly.

 

If you navigate through the PI-Web-API-Client-library-for-AngularJS\src\API\Client folder, you will see files with ts extension, which are TypeScript files. We need to convert them to JavaScript files since we want to generate a JavaScript AngularJS service.

 

Let's start installing TypeScript gloabally with the command:

 

npm install -g typescript@2.3.0 

 

 

Note 1: I wasn't able to compile the project using typescript 2.4.2. This is why you must be using typescript 2.3.0 to avoid errors on the angular.d.ts definitions.

Note 2: you must have NodeJS installed. Click here to download in case this product is not installed on your machine.

 

Open Visual Studio Code and click on File --> Open Folder. And select the PI-Web-API-Client-library-for-AngularJS folder. Click on View --> Integrated Terminal and type on the terminal:

 

tsc --init

 

This will create the tsconfig.json file on the root folder. This file should be used to set up options for the TypeScript compiler.

 

Next, press SHIFT + CTRL + P to show all the commands available and type "task" and click on "Tasks:Configure Task Runner". Press enter and select "TypeScript - tsconfig.json". This will create the file tasks.json under the .vscode folder. When you compile the project, Visual Studio Code will read the content of this tasks.json file and run the TypeScript compiler. If you open the tasks.json, you will realize that the "command" field is "tsc".

 

Let's compile this product by pressing SHIFT + CTRL +B and see what happens..... If you want to learn about Visual Studio Code key shortcuts, click here.

 

Nevertheless, there are more than 99 errors:

 

pastedImage_4.png

 

The main error is  "Cannot find namespace 'ng'.  This means that we need to add the AngularJS typescript library in order to compile our project."

 

Let's install Bower to download the AngularJS TypeScript library:

 

npm install -g bower 

 

And then type:

 

bower install angular-ts

 

This will create a folder bower_components and some subfolders:

 

pastedImage_2.png

 

The file we need to reference is bower_components\angular-ts\angular.d.ts which contains the ng TypeScript namespace.

 

The file that we need to change in order to add this reference is api.d.ts. All other files located on the API\Client folder reference this file.

 

Open the api.d.ts file on the editor and add the following line.

 

/// <reference path="../../../bower_components/angular-ts/angular.d.ts" />

 

Let's compile again by pressing SHIFT + CTRL +B. Now we receive a difference type of error:

 

pastedImage_3.png

 

The problem is with the following function which is present on all *Api.ts files:

 

        private extendObj<T1,T2>(objA: T1, objB: T2) {
            for(let key in objB){
                if(objB.hasOwnProperty(key)){
                    objA[key] = objB[key];
                }
            }
            return <T1&T2>objA;
        }

 

 

The solution is to replace T1 and T2 to any when defining the input variables:

 

        private extendObj<T1,T2>(objA: any, objB: any) {
            for(let key in objB){
                if(objB.hasOwnProperty(key)){
                    objA[key] = objB[key];
                }
            }
            return <T1&T2>objA;
        }

 

You can use Find & Replace in Files feature of Visual Studio Code to fix this problem on the 34 files.

 

 

pastedImage_1.png

 

 

Finally, if you compile again, you won't see any errors.

 

Creating the PI Web API AngularJS Module

 

 

Create a new PIWebApi.ts file under the src\ts folder with the following content:

 

class PIWebApi {


private basePath : string
private useKerberos : boolean
private username : string
private password : string
public defaultHeaders : any = {}
public analysis : OSIsoft.PIDevClub.PIWebApiClient.AnalysisApi
public analysisCategory : OSIsoft.PIDevClub.PIWebApiClient.AnalysisCategoryApi
public analysisRule : OSIsoft.PIDevClub.PIWebApiClient.AnalysisRuleApi
public analysisRulePlugIn : OSIsoft.PIDevClub.PIWebApiClient.AnalysisRulePlugInApi
public analysisTemplate : OSIsoft.PIDevClub.PIWebApiClient.AnalysisTemplateApi
public assetDatabase : OSIsoft.PIDevClub.PIWebApiClient.AssetDatabaseApi
public assetServer : OSIsoft.PIDevClub.PIWebApiClient.AssetServerApi
public attribute : OSIsoft.PIDevClub.PIWebApiClient.AttributeApi
public attributeCategory : OSIsoft.PIDevClub.PIWebApiClient.AttributeCategoryApi
public attributeTemplate : OSIsoft.PIDevClub.PIWebApiClient.AttributeTemplateApi
public attributeTrait : OSIsoft.PIDevClub.PIWebApiClient.AttributeTraitApi
public batch : OSIsoft.PIDevClub.PIWebApiClient.BatchApi
public calculation : OSIsoft.PIDevClub.PIWebApiClient.CalculationApi
public configuration : OSIsoft.PIDevClub.PIWebApiClient.ConfigurationApi
public dataServer : OSIsoft.PIDevClub.PIWebApiClient.DataServerApi
public element : OSIsoft.PIDevClub.PIWebApiClient.ElementApi
public elementCategory : OSIsoft.PIDevClub.PIWebApiClient.ElementCategoryApi
public elementTemplate :OSIsoft.PIDevClub.PIWebApiClient. ElementTemplateApi
public enumerationSet : OSIsoft.PIDevClub.PIWebApiClient.EnumerationSetApi
public enumerationValue : OSIsoft.PIDevClub.PIWebApiClient.EnumerationValueApi
public eventFrame : OSIsoft.PIDevClub.PIWebApiClient.EventFrameApi
public home : OSIsoft.PIDevClub.PIWebApiClient.HomeApi
public point : OSIsoft.PIDevClub.PIWebApiClient.PointApi
public securityIdentity : OSIsoft.PIDevClub.PIWebApiClient.SecurityIdentityApi
public securityMapping : OSIsoft.PIDevClub.PIWebApiClient.SecurityMappingApi
public stream : OSIsoft.PIDevClub.PIWebApiClient.StreamApi
public streamSet : OSIsoft.PIDevClub.PIWebApiClient.StreamSetApi
public system : OSIsoft.PIDevClub.PIWebApiClient.SystemApi
public table : OSIsoft.PIDevClub.PIWebApiClient.TableApi
public tableCategory : OSIsoft.PIDevClub.PIWebApiClient.TableCategoryApi
public timeRule : OSIsoft.PIDevClub.PIWebApiClient.TimeRuleApi
public timeRulePlugIn : OSIsoft.PIDevClub.PIWebApiClient.TimeRulePlugInApi
public unit : OSIsoft.PIDevClub.PIWebApiClient.UnitApi
public unitClass : OSIsoft.PIDevClub.PIWebApiClient.UnitClassApi
private httpService : any
private httpParamSerializer : any
private base64 : any




constructor(httpService : any, httpParamSerializer: any, base64 : any) {
this.httpService = httpService;
this.httpParamSerializer = httpParamSerializer;
this.base64 = base64;
}


public ConfigureInstance(basePath: string, useKerberos: boolean, username?: string, password?: string) {

this.basePath = basePath;
this.useKerberos = useKerberos;
if (this.useKerberos == false)
{
this.username = username;
this.password = password;
if (this.base64 != null)
{
var auth = this.base64.encode(this.username + ":" + this.password);
this.httpService.defaults.headers.common['Authorization'] = 'Basic ' + auth;
}
}
else
{
this.httpService.defaults.withCredentials = true;
this.httpService.defaults.useXDomain = true;
}
this.analysis = new OSIsoft.PIDevClub.PIWebApiClient. AnalysisApi(this.basePath, this.httpService, this.httpParamSerializer);
this.analysisCategory = new OSIsoft.PIDevClub.PIWebApiClient. AnalysisCategoryApi(this.basePath, this.httpService, this.httpParamSerializer);
this.analysisRule = new OSIsoft.PIDevClub.PIWebApiClient. AnalysisRuleApi(this.basePath, this.httpService, this.httpParamSerializer);
this.analysisRulePlugIn = new OSIsoft.PIDevClub.PIWebApiClient. AnalysisRulePlugInApi(this.basePath, this.httpService, this.httpParamSerializer);
this.analysisTemplate = new OSIsoft.PIDevClub.PIWebApiClient. AnalysisTemplateApi(this.basePath, this.httpService, this.httpParamSerializer);
this.assetDatabase = new OSIsoft.PIDevClub.PIWebApiClient. AssetDatabaseApi(this.basePath, this.httpService, this.httpParamSerializer);
this.assetServer = new OSIsoft.PIDevClub.PIWebApiClient. AssetServerApi(this.basePath, this.httpService, this.httpParamSerializer);
this.attribute = new OSIsoft.PIDevClub.PIWebApiClient. AttributeApi(this.basePath, this.httpService, this.httpParamSerializer);
this.attributeCategory = new OSIsoft.PIDevClub.PIWebApiClient. AttributeCategoryApi(this.basePath, this.httpService, this.httpParamSerializer);
this.attributeTemplate = new OSIsoft.PIDevClub.PIWebApiClient. AttributeTemplateApi(this.basePath, this.httpService, this.httpParamSerializer);
this.attributeTrait= new OSIsoft.PIDevClub.PIWebApiClient. AttributeTraitApi(this.basePath, this.httpService, this.httpParamSerializer);
this.batch = new OSIsoft.PIDevClub.PIWebApiClient. BatchApi(this.basePath, this.httpService, this.httpParamSerializer);
this.calculation= new OSIsoft.PIDevClub.PIWebApiClient. CalculationApi(this.basePath, this.httpService, this.httpParamSerializer);
this.configuration = new OSIsoft.PIDevClub.PIWebApiClient. ConfigurationApi(this.basePath, this.httpService, this.httpParamSerializer);
this.dataServer = new OSIsoft.PIDevClub.PIWebApiClient. DataServerApi(this.basePath, this.httpService, this.httpParamSerializer);
this.element = new OSIsoft.PIDevClub.PIWebApiClient. ElementApi(this.basePath, this.httpService, this.httpParamSerializer);
this.elementCategory= new OSIsoft.PIDevClub.PIWebApiClient. ElementCategoryApi(this.basePath, this.httpService, this.httpParamSerializer);
this.elementTemplate = new OSIsoft.PIDevClub.PIWebApiClient. ElementTemplateApi(this.basePath, this.httpService, this.httpParamSerializer);
this.enumerationSet = new OSIsoft.PIDevClub.PIWebApiClient. EnumerationSetApi(this.basePath, this.httpService, this.httpParamSerializer);
this.enumerationValue = new OSIsoft.PIDevClub.PIWebApiClient. EnumerationValueApi(this.basePath, this.httpService, this.httpParamSerializer);
this.eventFrame = new OSIsoft.PIDevClub.PIWebApiClient. EventFrameApi(this.basePath, this.httpService, this.httpParamSerializer);
this.home = new OSIsoft.PIDevClub.PIWebApiClient. HomeApi(this.basePath, this.httpService, this.httpParamSerializer);
this.point = new OSIsoft.PIDevClub.PIWebApiClient. PointApi(this.basePath, this.httpService, this.httpParamSerializer);
this.securityIdentity = new OSIsoft.PIDevClub.PIWebApiClient. SecurityIdentityApi(this.basePath, this.httpService, this.httpParamSerializer);
this.securityMapping = new OSIsoft.PIDevClub.PIWebApiClient. SecurityMappingApi(this.basePath, this.httpService, this.httpParamSerializer);
this.stream = new OSIsoft.PIDevClub.PIWebApiClient. StreamApi(this.basePath, this.httpService, this.httpParamSerializer);
this.streamSet = new OSIsoft.PIDevClub.PIWebApiClient. StreamSetApi(this.basePath, this.httpService, this.httpParamSerializer);
this.system = new OSIsoft.PIDevClub.PIWebApiClient. SystemApi(this.basePath, this.httpService, this.httpParamSerializer);
this.table = new OSIsoft.PIDevClub.PIWebApiClient. TableApi(this.basePath, this.httpService, this.httpParamSerializer);
this.tableCategory = new OSIsoft.PIDevClub.PIWebApiClient. TableCategoryApi(this.basePath, this.httpService, this.httpParamSerializer);
this.timeRule = new OSIsoft.PIDevClub.PIWebApiClient. TimeRuleApi(this.basePath, this.httpService, this.httpParamSerializer);
this.timeRulePlugIn = new OSIsoft.PIDevClub.PIWebApiClient. TimeRulePlugInApi(this.basePath, this.httpService, this.httpParamSerializer);
this.unit = new OSIsoft.PIDevClub.PIWebApiClient. UnitApi(this.basePath, this.httpService, this.httpParamSerializer);
this.unitClass = new OSIsoft.PIDevClub.PIWebApiClient. UnitClassApi(this.basePath, this.httpService, this.httpParamSerializer);
}
}

 

 

Create a new ngService.ts file under the src\ts folder with the following content:

 

/// <reference path="api.d.ts"/>

angular.module('ngPIWebApi',['base64']).factory('piwebapi', ['$http', '$httpParamSerializer', '$base64',function ($http: ng.IHttpService, $httpParamSerializer: any, $base64: any) {
    let piwebapi = new PIWebApi($http, $httpParamSerializer, $base64);
return piwebapi;
}]);

 

 

The code above creates a new AngularJS module called ngPIWebApi that required the base64 module to generate the Basic Authentication header. Therefore, let's add the base64 module to our bower components:

 

bower install angular-base64

 

The ConfigureInstance method allows you to define the PI Web API endpoint URL and set up which type of security you want to use (Anonymous, Basic or Kerberos). It will also map he piwebapi properties with the API objects. As a result, all API methods are available under the piwebapi object. If it is confused for now, don't worry. The last section of this post is about creating a web application using this library.

 

Using Gulp to concatenate and minify

 

According to Wikipedia:

 

Gulp is a build tool in JavaScript built on node streams. These streams facilitate the connection of file operations through pipelines.Gulp reads the file system and pipes the data at hand from its one single-purposed plugin to other through the .pipe() operator, doing one task at a time. The original files are not affected until all the plugins are processed. It can be configured either to modify the original files or to create new ones. This grants the ability to perform complex tasks through linking its numerous plugins. The users can also write their own plugins to define their own tasks.Unlike other task runners that run tasks by configuration, gulp requires knowledge of JavaScript and coding to define its tasks. Gulp is a build system which means apart from running tasks, it is also capable of copying files from one location to another, compiling, deploying, creating notifications, unit testing, linting etc.

 

Let's first install gulp and some other tools through npm:

 

npm install gulp gulp-concat gulp-uglify

 

Then, create a new file on the root named gulpfile.js with the following content:

 

var gulp = require('gulp');
var concat = require('gulp-concat');  
var uglify = require("gulp-uglify");


//script paths
var jsDest = 'dist';




gulp.task('default', ['concatenate', 'concatenateAndMinify', 'concatenateForPIVision', 'concatenateAndMinifyForPIVision']);


gulp.task('concatenateAndMinify', function() {  
    return gulp.src(['src/js/api/*.js','src/js/models/*.js', 'src/js/PIWebApi.js', 'src/js/ngService.js'])  
        .pipe(concat('angular-piwebapi.min.js'))
        .pipe(uglify())  
        .pipe(gulp.dest(jsDest));       
});



gulp.task('concatenate', function() {  
    return gulp.src(['src/js/api/*.js','src/js/models/*.js','src/js/PIWebApi.js', 'src/js/ngService.js'])  
        .pipe(concat('angular-piwebapi.js'))
        .pipe(gulp.dest(jsDest));       
});


gulp.task('concatenateAndMinifyForPIVision', function() {  
    return gulp.src(['src/js/api/*.js', 'src/js/models
/*.js','src/js/PIWebApi.js', 'src/js/ngServiceForPIVision.js'])  
        .pipe(concat('angular-piwebapi-kerberos.min.js'))
        .pipe(uglify())  
        .pipe(gulp.dest(jsDest));       
});




gulp.task('concatenateForPIVision', function() {  
    return gulp.src(['src/js/api/*.js', 'src/js/models/*.js','src/js/PIWebApi.js', 'src/js/ngServiceForPIVision.js']) 
        .pipe(concat('angular-piwebapi-kerberos.js'))
        .pipe(gulp.dest(jsDest));       
});

 

And change the .vscode\tasks.json content to:

 

{
    "version": "0.1.0",
    "command": "gulp",
    "isShellCommand": true,
    "args": [
        "--no-color"
    ],
    "tasks": [ {
  "taskName": "default",
  "isBuildCommand": true,
  "showOutput": "always"
  }
  ]
}

 

 

The code above will concatenate all JavaScript from src/ts  to generate the angular-piwebapi.js file to the dist folder. It also generate a minified version of this file (piwebapi.min.js). Build this project and check if both files were created on dist folder. This module requires the angular-base64.js and angular-base64.min.js JavaScript libraries. This is why I have manually copied those files to the dist folder.

 

Note: With this change, when pressing SHIFT + CTRL + B the TypeScript compiler won't be called anymore, unless you configure it on gulpfile.js to use gulp-typescript.

 

Developing a web application using AngularJS and PI Web API client libraries

 

In order to test this new client library, let's take the example from the blog post. First we need to edit the index.html:

 

    <script src="Scripts/angular.min.js"></script>
    <script src="Scripts/angular-base64.min.js"></script>
    <script src="Scripts/piwebapi.min.js"></script>
    <script src="Scripts/app.js"></script>

 

 

The original app.js is:

 

var piWebApiApp = angular.module("PiWebApiSampleApp", []);


piWebApiApp.controller("mainCtrl", function ($scope, piWebApiHttpService) {


    //declare and inicialize variables
    $scope.requestMode = true;
    $scope.getSnap = true;
    $scope.getRec = true;
    $scope.getInt = true;


    //options for the combobox on the initial page
    $scope.yesOrNoOptions = [{ "value": true, "name": "Yes" }, { "value": false, "name": "No" }];


    //update values when the default button is pressed
    $scope.defaultValues = function () {
        $scope.piServerName = "MARC-PI2014";
        $scope.piPointName = "SINUSOID";
        $scope.startTime = "*-1d";
        $scope.endTime = "*";
        $scope.interval = "1h";
        $scope.getSnap = $scope.yesOrNoOptions[0];
        $scope.getRec = $scope.yesOrNoOptions[0];
        $scope.getInt = $scope.yesOrNoOptions[0];
    }


    //get data by making http calls
    $scope.getData = function () {
        //switch div to display the results
        $scope.requestMode = false;
        //all HTTP requests are done through the  piWebApiHttpService factory object
        piWebApiHttpService.validPIServerName($scope.piServerName).then(function (response) {
            //this function will be executed in case of success
            $scope.piServerData = response.data;
            $scope.piServerExistsValue = true;
        }, function (error) {
            //this function will be executed in case of error
            $scope.piServerError = error.data;
            $scope.piServerExistsValue = false;
        });


        piWebApiHttpService.validPIPointName($scope.piServerName, $scope.piPointName).then(function (response) {
            $scope.piPointData = response.data;
            $scope.piPointExistsValue = true;
            //in case of success, we will get the webId of the PI Point which will be used by other requests
            $scope.webId = response.data.WebId;
            piWebApiHttpService.getSnapshotValue($scope.webId).then(function (response) {
                //Response of the snapshot is stored on the snapshotData
                $scope.snapshotData = response.data;
            }, function (error) {
                $scope.snapshotError = error.data;


            });
            //The following requests use the webId already stored
            piWebApiHttpService.getRecordedValues($scope.webId, $scope.startTime, $scope.endTime).then(function (response) {
                $scope.recordedData = response.data;
            }, function (error) {
                $scope.recordedError = error.data;
            });


            piWebApiHttpService.getInterpolatedValues($scope.webId, $scope.startTime, $scope.endTime, $scope.interval).then(function (response) {
                $scope.interpolatedData = response.data;
            }, function (error) {
                $scope.interpolatedError = error.data;
            });
        }, function (error) {
            $scope.piPointError = error.data;
            $scope.piPointExistsValue = false;
        });
    }
});

 

The new app.js looks like:

 

var piWebApiApp = angular.module("demo-app", ['ngPIWebApi']);


piWebApiApp.run(function (piwebapi) {
    piwebapi.ConfigureInstance("https://marc-web-sql.marc.net/piwebapi", true);
});




piWebApiApp.controller("mainCtrl", function ($scope, piwebapi) {   




    // //declare and inicialize variables
    $scope.requestMode = true;
    $scope.getSnap = true;
    $scope.getRec = true;
    $scope.getInt = true;


    // //options for the combobox on the initial page
    $scope.yesOrNoOptions = [{ "value": true, "name": "Yes" }, { "value": false, "name": "No" }];


    // //update values when the default button is pressed
    $scope.defaultValues = function () {
        $scope.piServerName = "MARC-PI2016";
        $scope.piPointName = "SINUSOID";
        $scope.startTime = "*-1d";
        $scope.endTime = "*";
        $scope.interval = "1h";
        $scope.getSnap = $scope.yesOrNoOptions[0];
        $scope.getRec = $scope.yesOrNoOptions[0];
        $scope.getInt = $scope.yesOrNoOptions[0];
    }


    // //get data by making http calls
    $scope.getData = function () {
        //switch div to display the results
        $scope.requestMode = false;
        //all HTTP requests are done through the  piWebApiHttpService factory object
        piwebapi.dataServer.getByPath('\\\\' + $scope.piServerName).then(function (response) {
            //this function will be executed in case of success
            $scope.piServerData = response.data;
            $scope.piServerExistsValue = true;
        }, function (error) {
            //this function will be executed in case of error
            $scope.piServerError = error.data;
            $scope.piServerExistsValue = false;
        });


        piwebapi.point.getByPath('\\\\' + $scope.piServerName + '\\' + $scope.piPointName, null, null).then(function (response) {
            $scope.piPointData = response.data;
            $scope.piPointExistsValue = true;
            //in case of success, we will get the webId of the PI Point which will be used by other requests
            $scope.webId = response.data.WebId;
            piwebapi.stream.getValue($scope.webId).then(function (response) {
                //Response of the snapshot is stored on the snapshotData
                $scope.snapshotData = response.data;
            }, function (error) {
                $scope.snapshotError = error.data;


            });


            piwebapi.stream.getRecorded($scope.webId, null, null, $scope.endTime, null, null, null, null, $scope.startTime).then(function (response) {
                $scope.recordedData = response.data;
            }, function (error) {
                $scope.recordedError = error.data;
            });


            piwebapi.stream.getInterpolated($scope.webId, null, $scope.endTime, null, null, $scope.interval, null, null, null, null, null, $scope.startTime).then(function (response) {
                $scope.interpolatedData = response.data;
            }, function (error) {
                $scope.interpolatedError = error.data;
            });
        }, function (error) {
            $scope.piPointError = error.data;
            $scope.piPointExistsValue = false;
        });
    }
});

 

Using the library, you don't need to write the piWebApiHttpService service. Just use this client library instead and it will work!

 

Conclusion

 

I really think that having PI Web API client libraries available for many different platforms with make our lives much easier. In this blog post I have described all the steps I had to do in order to generate the library. Nevertheless, you can just download it from this GitHub repository without having to generate it yourself. The next blog post will be about integrating this library in PI Vision! Stay tuned!

This is the material for the "Developing Cross-Platform Mobile Apps using Xamarin and PI Web API " hands-on-lab held during the UC SF 2017 Developer Track.

 

https://github.com/osimloeff/Xamarin-TechCon2017

 

It includes:

1. Visual Studio Solution with two 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.

 

This lab has 5 exercises.

  • Exercises 1 and 5 will focus on PI Web API calls.
  • Exercise 3 will explain how integrate your app with PI Coresight.
  • Exercises 2 and 4 are about Xamarin.Forms.

 

If you want to have another hands-on-lab next year about developing mobile apps using Xamarin or if you have any other comments or questions, please post a comment on PI Square.

We are excited to present the Users Conference Programming Hackathon 2017 winners!

 

The theme of this year's Hackathon was IIoT: Asset Health Monitoring, Predictive Analytics, and Maintenance Optimization of industrial mobile assets. Barrick Gold Corporation, the largest mining company in the world, kindly provided a sample of one of their sites with haul trucks data. Participants were encouraged to create killer applications for Barrick by leveraging the PI System infrastructure.

 

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

  • PI Server 2017 Beta
  • PI Web API 2017 Beta
  • PI Vision 2017  Beta
  • PI OLEDB Enterprise 2016 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, seven groups were able to finish their app and present to the judges!

 

Prizes:

1st place: $400 Amazon gift card, one year free subscription to PI Developers Club, one time free registration at OSIsoft Users Conference over the next 1 year

2nd place: $300 Amazon gift card, one year free subscription to PI Developers Club, 50% discount for registration at OSIsoft Users Conference over the next 1 year

3rd place: $200 Amazon gift card, one year free subscription to PI Developers Club

 

 

Without further do, here are the winners!

 

1st place - Random Sample

 

The team members were: Jacqueline Davis, James Hughes, Matthew Wallace and Jon Horton.

 

IMG_2072.JPG

 

Team Random Sample developed an app for the drivers of haul trucks. They receive points for optimal driving and reporting of road hazards. The displays of the app support driver interaction. On top of that, they have added a bar code security to access the web site.

 

The team used the following technologies:

  • PI Vision
  • Google Maps
  • Node.JS

 

Here are some screenshots presented by the Random Sample team!

 

 

 

 

 

2nd place - Machine Learners

 

The team members were: Ionut Buse, Gael Cottet and Jean-Francois Beaulieu

 

IMG_2071.JPG

 

 

They developed an app named Truck learning. It is a predictive analytics application, which uses Azure machine learning to predict fuel consumption statistics based on different attributes of a haul truck's trip.

 

The team used the following technologies:

  • Azure Machine Learning
  • AngularJS
  • AF-SDK
  • Azure Web Services

 

Here are some screenshots presented by Machine Learners!

 

 

 

 

 

3rd place - Atomic 79

 

The team members were: Mina Andrawos, Stew Bernhardt, Seth Gregg and Dave Johnson.

 

IMG_2064.JPG

 

 

Team Atomic 79 developed an app named Barrick Tomcat, which is actually a suite of the following 3 apps:

  • Color LED data visualization of truck health
  • Intelligent bot for truck status
  • Convert human voice to PI tag values for truck driver to annotate PI data

 

The team used the following technologies:

  • Node.js (axios, firmata, node-pixel)
  • Microsoft Bot Framework
  • Azure SDK
  • Microsoft Cognitive Services

 

Here are some screenshots presented by Atomic 79!

 

 

 

3rd place - PI in the sky

 

The team members were: Rhys Kirk, Rob Raesemann, Yevgeni Nogin and Lonnie Bowling.

 

IMG_2083.JPG

 

PI in the sky team created the Haul Truck Monitoring and Performance app. The app provides analytics and visualization of Haul Truck Performance including pattern recognition, custom dashboard, and PI Vision displays.

 

The team used the following technologies:

  • Angular 2
  • D3
  • Python
  • Asyncio

 

Here are some screenshots presented by PI in the sky!

 

 

Introduction to Xamarin

 

Xamarin is a product that brings .NET/C# to both Android and iOS. Xamarin is pretty amazing in that it's fully .NET while being able to produce true Android and iOS apps at the same time, and apps that are compliant with the distribution requirements of both Google Play and the iOS App Store. On this blog post, we will develop a sample application developed using Xamarin Android that will retrieve the current value of sinusoid PI Point from a PI System on the cloud through PI Web API.

 

The sample application

 

In order to develop an Android application using Xamarin, you can use:

 

  • Xamarin Studio on a Mac
  • Visual Studio 2015 with Xamarin

 

Both options work great. In my case, I will be using Visual Studio.

 

If you prefer, the source code package is available on this GitHub repository.

 

Create a new project, select Visual C# --> Android --> Blank App.

 

 

Add a Portable Class Library for iOS, Android and Windows called PIWebAPIWrapper to your solution. This new project would be compatible with Android, iOS and Windows. Therefore, it is interesting to write all your business logic and models on this library specially if you plan to create an iOS version of your app. In this case, you will be able to integrate the Portable Class Library with your future iOS project.

 

 

Add the Microsoft.Net.Http NuGet package to your Portable Class library.

 

 

Repeat the same procedure for the Newtonsoft.Json NuGet package.

 

It is always a good practice to rebuild all your solution after adding packages to your Xamarin proejcts.

 

Let's start writing code to the Portable Class library first. Rename Class1 to PIWebApiService and add the following code to the file content:

 

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;


namespace PIWebAPIWrapper
{
    public class PIWebApiService
    {
        private string baseUrl = "https://webserver/piwebapi/";
        private string piDataArchiveName = "piservername";
        private string username = "username";
        private string password = "password";


        public async Task<string> DownloadWebData(string url)
        {
            var handler = new HttpClientHandler();
            handler.Credentials = new NetworkCredential(username, password);
            HttpClient httpClient = new HttpClient(handler);
            HttpResponseMessage httpMessage = await httpClient.GetAsync(url);
            if (httpMessage.IsSuccessStatusCode == true)
            {
                using (var stream = await httpMessage.Content.ReadAsStreamAsync())
                {
                    using (var streamReader = new StreamReader(stream))
                    {
                        return await streamReader.ReadToEndAsync();
                    }
                }
            }
            return string.Empty;
        }




        public async Task<double> GetSinusoidValue()
        {
            string url = string.Format(baseUrl + "points?path=\\\\{0}\\sinusoid", piDataArchiveName);
            string response = await DownloadWebData(url);
            JObject data = JsonConvert.DeserializeObject<dynamic>(response);
            string webId = data["WebId"].ToString();
            url = string.Format(baseUrl + "streams/{0}/value", webId);
            response = await DownloadWebData(url);
            JObject dataValue = JsonConvert.DeserializeObject<dynamic>(response);
            double value = Convert.ToDouble(dataValue["Value"].ToString());           
            return value;
        } 
    }
}

 

 

The PIWebApiService is responsable for getting the current value of the sinusoid PI Point by making two HTTP requests:

  1. Get the WebId of the sinusoid PI Point
  2. Get the current value of the sinusoid using the WebId returned on the previous step.

 

Let's move to the Android project. The main activity is the one that is going to appear once the application starts.

 

The layout of the MainActivity has mainly 3 objects:

 

  • TextView which will show the value of sinusoid
  • ProgressBar which will show a loading spinner while the app is making the HTTP requests
  • LinearLayout which defines the location of TextView and ProgressBar within the screen.

 

 

The code of the main.axml layout is below:

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:gravity="center"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/sinusoid_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

 

Finally, the code of the MainActivity is below:

 

using Android.App;
using Android.Widget;
using Android.OS;
using PIWebAPIWrapper;
using System.Threading.Tasks;


namespace XamarinPIWebAPIApp
{
    [Activity(Label = "PI Web API Sample Xamarin App", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        TextView sinusoidEditText = null;
        ProgressBar progressBar = null;
        Task t = null;


        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            SetContentView(Resource.Layout.Main);
            sinusoidEditText = FindViewById<TextView>(Resource.Id.sinusoid_text);
            progressBar = FindViewById<ProgressBar>(Resource.Id.progressBar);
            PIWebApiService service = new PIWebApiService();
            double value = -1;
            t  = Task.Run(async () =>
            {
                value = await service.GetSinusoidValue();
                this.RunOnUiThread(() =>
                {
                    progressBar.Visibility = Android.Views.ViewStates.Invisible;
                    sinusoidEditText.Text = string.Format("The current sinusoid value is {0}", value);
                });


            });
        }
    }
}

 

 

Here are the steps of what happens on the MainActivity:

 

  1. It renders the Main.axml layout.
  2. It finds the TextView and the ProgressBar and store them on private variables.
  3. It creates a new Task which will call:
    1. Service.GetSinusoidValue() to get the current value of the sinusoid.
    2. Once the value is retrieved, the progressBar needs to disappear and the value should appear on the text of the sinusoidEditText UI object.
    3. These lines of code will manipulate the UI. This is why it needs to be executed inside an Action which would be an input of the RunOnUiThread.

 

Conclusions

 

This blog post is about developing Android application using the native Xamarin Android. I think this is a really interesting option for us since our community is very familiar with C# and Android is one of the most popular mobile platforms in the market.

 

Please don't hesitate to share your comments below!

Introduction

 

One of my most read blog post is about Using PI Web API on HTML with jQuery. Some time later due to the populatity of AngularJS, I have written another blog post about the same topic but using AngularJS instead of jQuery.

 

AngularJS has become one of the most popular JavaScript framework of the market. It is a toolset for building the framework most suited to your application development. It is extensible and works well with other libraries. Every feature can be modified or superseded to suit your unique development workflow and feature needs.

 

But web technology changes really fast. AngularJS was born in 2009 and after 6 years collecting feedback, Google has recently released Angular 2.

 

 

Why Angular 2?

 

There are a lot of articles that explains the benefits of choosing Angular 2 on your new web projects:

 

 

In a nutshell, the advantages of using Angular 2 are:

 

  • Better speed and performance
  • Easier to use when compared to AngularJS
  • Mobile driven
  • Cross platform, as it allows you to develop web apps, native mobile apps and desktop apps.

 

Using Typescript with Angular 2

 

You may be surprised with the fact that Angular 2 from Google was written using TypeScript from Microsoft.

 

TypeScript is a superset of JavaScript which primarily provides optional static typing, classes and interfaces. One of the big benefits is to enable IDEs to provide a richer environment for spotting common errors as you type the code.

To get an idea of what I mean, watch Microsoft's introductory video on the language.

For a large JavaScript project, adopting TypeScript might result in more robust software, while still being deployable where a regular JavaScript application would run.

Although you might use Angular 2 with JavaScript directly (ES5 or ES6), the recommended way to use Angular 2 is with TypeScript.

For more information, please refer to this article.

You can install Typescript using NodeJS/npm:

 

npm install -g typescript

 

 

Getting started with Visual Studio Code

 

After watching some video tutorials about using Angular 2, it seems pretty clear that the recommended IDE for developing a web application with Angular 2 is through Visual Studio Code, which is free, cross platform, fast and lightweight code editor developed by Microsoft for Windows, Linux and Mac. Please refer to this article for more information about the difference between both IDEs.

 

If you don't have Visual Studio Code, download it here.

 

Using Angular CLI

 

Together with Angular 2, the Angular2 CLI was also released to help you create an application that already works out of the box. You can install Angular CLI using npm through the Command Promt:

 

npm install -g angular-cli

 

Once it is installed use the command below to create our Angular 2 project:

 

ng new PI-Web-API-With-Angular2

 

It can take some minutes to install all packages for tooling via npm.

 

Once it has finished, run the web application using the following commands:

 

cd PI-Web-API-With-Angular2
ng serve

 

If you open the browser and navigate to http://localhost:4200, you will see your application showing "app works!".

 

The sample application

 

Let's start developing our sample application! If you want to download the source code package, just visit this GitHub repository.

 

Close the command promt, start Visual Studio code and open the project folder.

 

I will show you the files that need to be added or edited in order to make your app work fine. The purpose of this blog post is not to teach you about Angular 2 but to provide a sample application for you to get started using this technology with PI Web API. If you are interested on the topic, there are many videos, articles and books available to learn Angular 2.

 

Let's start editing the app.module.ts with the following content:

 

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';


import { AppComponent } from './app.component';
import { PIWebAPIService } from './piwebapi.service';




@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [PIWebAPIService],
  bootstrap: [AppComponent]
})
export class AppModule { }

 

Create a new file called piwebapi.service.ts under the /src/app folder, which is a TypeScript file that will create the service for making HTTP request against PI Web API. The content of this file is below:

 

import { Injectable } from '@angular/core';
import { Headers, Http, Response } from '@angular/http';
import { Observable } from "rxjs/Observable";






@Injectable()
export class PIWebAPIService {
    private piWebApiBaseUrl = 'https://marc-web-sql.marc.net/piwebapi';  // URL to web api
    constructor(private http: Http)
     { 


     }


     getHeader() : any {
         var authHeader = new Headers();
         authHeader.append('Authorization', 'Basic ' + btoa('username:password')); 
         return authHeader;
     }


     validPIServerName(piServerName : string) : any { 
         return this.http.get(this.piWebApiBaseUrl + "dataservers?name=" + piServerName,  {headers: this.getHeader() });
    };




     validPIPointName(piServerName : string, piPointName : string) : any {
         return this.http.get(this.piWebApiBaseUrl + "points?path=\\\\" + piServerName + "\\" + piPointName,  {headers: this.getHeader() });
    };




    getSnapshotValue(webId:string) : any {
         return this.http.get(this.piWebApiBaseUrl + 'streams/' + webId + '/value',  {headers: this.getHeader() });
    };


    getRecordedValues(webId : string, startTime : string, endTime : string) {
        return this.http.get(this.piWebApiBaseUrl + 'streams/' + webId + '/recorded?starttime=' + startTime + '&endtime=' + endTime,  {headers: this.getHeader() });
    };


   getInterpolatedValues(webId : string, startTime  :string, endTime : string, interval : string) {
        return this.http.get(this.piWebApiBaseUrl  + 'streams/' + webId + '/interpolated?starttime=' + startTime + '&endtime=' + endTime + "&interval=" + interval,  {headers: this.getHeader() });
    };
}

 

Edit the app.component.ts that has all the JavaScript logic of your app, which will call the PI Web API service in order to make the HTTP calls. It has the following content:

 

import { Component } from '@angular/core';
import { PIWebAPIService } from './piwebapi.service';




export class ComboboxOption {
  value: boolean;
  name: string;
}


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
  requestMode = true;
  piServerName: string;
  piPointName: string;
  startTime: string;
  endTime: "*";
  interval: "1h";
  yesOrNoOptions: ComboboxOption[] = [{ "value": true, "name": "Yes" }, { "value": false, "name": "No" }];
  getSnap: ComboboxOption = this.yesOrNoOptions[0];
  getRec: ComboboxOption = this.yesOrNoOptions[0];
  getInt: ComboboxOption = this.yesOrNoOptions[0];
  piServerData: any;
  piServerExistsValue: boolean;
  piServerError: any;
  piPointData: any;
  piPointExistsValue: boolean;
  webId: string;
  snapshotData: any;
  snapshotError: any;
  recordedData: any;
  interpolatedData: any;
  recordedError: any;
  interpolatedError: any;
  piPointError: any;


  constructor(private piWebApiHttpService: PIWebAPIService) { }


  defaultValues(): void {
    this.piServerName = "PISERVERNAME";
    this.piPointName = "SINUSOID";
    this.startTime = "*-1d";
    this.endTime = "*";
    this.interval = "1h";
    this.getSnap = this.yesOrNoOptions[0];
    this.getRec = this.yesOrNoOptions[0];
    this.getInt = this.yesOrNoOptions[0];
  }


  //get data by making http calls
  getData(): void {
    //switch div to display the results
    this.requestMode = false;
    //all HTTP requests are done through the  piWebApiHttpService factory object
    this.piWebApiHttpService.validPIServerName(this.piServerName)
      .subscribe(res => {
        this.piServerData = res.json();
        this.piServerExistsValue = true;
      },
      error => {
        this.piServerError = error.json();
        this.piServerExistsValue = false;
      });




    this.piWebApiHttpService.validPIPointName(this.piServerName, this.piPointName).subscribe(res => {
      this.piPointData = res.json();
      this.piPointExistsValue = true;
      //in case of success, we will get the webId of the PI Point which will be used by other requests
      this.webId = res.json().WebId;
      this.piWebApiHttpService.getSnapshotValue(this.webId).subscribe(res => {
        this.snapshotData = res.json();
      }, error => {
        this.snapshotError = error.json();
      });
      //The following requests use the webId already stored
      this.piWebApiHttpService.getRecordedValues(this.webId, this.startTime, this.endTime).subscribe(res => {
        this.recordedData = res.json();
      }, error => {
        this.recordedError = error.json();
      });


      this.piWebApiHttpService.getInterpolatedValues(this.webId, this.startTime, this.endTime, this.interval).subscribe(res => {
        this.interpolatedData = res.json();
      }, error => {
        this.interpolatedError = error.json();
      });
    }, error => {
      this.piPointError = error.data;
      this.piPointExistsValue = false;
    });
  }
}

 

The file app.component.html has the HTML markup of your app. The content of this file should be modified to:

 

<div [hidden]="requestMode==false">
  <h1>Request PI Data</h1>
  <p>
  Please fill in your details below and click  on &quot;Get PI Data&quot;. Fields marked with an asterisk (*) are required.
  </p>
  <div>
  <div style="width: 30em;">
  <label for="piServerName">PI Server Name *</label>
  <!--The value of the input below is bound to the value of the $scope.piServerName.-->
  <input type="text" name="piServerName" id="piServerName" [(ngModel)]="piServerName" />
  <label for="piPointName">PI Point name *</label>
  <input type="text" name="piPointName" id="piPointName" [(ngModel)]="piPointName" />
  <label for="startTime">Start time *</label>
  <input type="text" name="startTime" id="startTime" [(ngModel)]="startTime" />
  <label for="endTime">End time *</label>
  <input type="text" name="endTime" id="endTime" [(ngModel)]="endTime" />
  <label for="interval">Interval *</label>
  <input type="text" name="interval" id="interval" [(ngModel)]="interval" />


  <label for="getsnap">Get Snapshot?</label>
  <select name="getsnap" id="getsnap" size="1" [(ngModel)]="getSnap">
        <option *ngFor="let option of yesOrNoOptions" [value]="option">{{option.name}}</option>
      </select>
  <label for="getrec">Get Recorded Data?</label>
  <select name="getrec" id="getrec" size="1" [(ngModel)]="getRec">
            <option *ngFor="let option of yesOrNoOptions" [value]="option">{{option.name}}</option>
      </select>


  <label for="getint">Get Interpolated Data?</label>
  <select name="getint" id="getint" size="1" [(ngModel)]="getInt">
            <option *ngFor="let option of yesOrNoOptions" [value]="option">{{option.name}}</option>
      </select>


  <div style="clear: both;">
  <!--This function will make Http request and store the results-->
  <input type="submit" id="submitButton" value="Get PI Data!" (click)="getData()" />
  <!--This function will change the values declared on $scope which are bound to some DOM elements-->
  <input type="button" id="DefaultButton" (click)="defaultValues()" value="Default Values" style="margin-right: 20px;" />
  </div>
  </div>
  </div>
</div>
<div *ngIf="requestMode==false">
  <h1>Displaying SINUSOID data</h1>


  <h2>Connection information</h2>
  <br />
  <table border="0" style="width: 20em; border: 1px solid #666;">
  <tr>
  <th>Property</th>
  <th>Value</th>
  </tr>


  <tr>
  <td>PI Server</td>
  <!--Using { {} } also binds the DOM with variables declared under $scope-->
  <td id="PIServerNameValue">{{piServerName}}</td>
  </tr>
  <tr>
  <td>PI Point</td>
  <td id="PIPointNameValue">{{piPointName}}</td>
  </tr>
  <tr>
  <td>Start time</td>
  <td id="StartTimeValue">{{startTime}}</td>
  </tr>
  <tr>
  <td>End time</td>
  <td id="EndTimeValue">{{endTime}}</td>
  </tr>
  <tr>
  <td>Interval</td>
  <td id="IntervalValue">{{interval}}</td>
  </tr>


  <tr>
  <td>Does the PI Server exist?</td>
  <td id="PIServerExistValue">{{piServerExistsValue}}</td>
  </tr>
  <tr>
  <td>Does the PI Point exist?</td>
  <td id="PIPointExistValue">{{piPointExistsValue}}</td>
  </tr>
  </table>


  <div id="errors">
  <div *ngIf="snapshotError && snapshotError.Errors">
  <p *ngFor="let error of snapshotError.Errors">{{error}}</p>
  </div>
  <div *ngIf="recordedError && recordedError.Errors">
  <p *ngFor="let error of recordedError.Errors">{{error}}</p>
  </div>
  <div *ngIf="interpolatedError && interpolatedError.Errors">
  <p *ngFor="let error of interpolatedError.Errors">{{error}}</p>
  </div>
  <div *ngIf="piServerError && piServerError.Errors">
  <p *ngFor="let error of piServerError.Errors">{{error}}</p>
  </div>
  <div *ngIf="piPointError && piPointError.Errors">
  <p *ngFor="let error of piPointError.Errors">{{error}}</p>
  </div>
  </div>
  <div *ngIf="getSnap.value == true &&webId != null">
  <h2 class="snapshot">Snapshot Value of Sinusoid</h2>
  <br />
  <table class="snapshot" border="0" style="width: 20em; border: 1px solid #666;">
  <tr>
  <th>Value</th>
  <th>Timestamp</th>
  </tr>
  <tr *ngIf="snapshotData">
  <td>{{snapshotData.Value}}</td>
  <td>{{snapshotData.Timestamp}}</td>
  </tr>
  </table>
  </div>
  <br />
  <br />


  <div *ngIf="getRec.value == true && recordedData && recordedData.Items">
  <h2 class="recorded">Recorded Values of Sinusoid</h2>
  <br />
  <table class="recorded" border="0" style="width: 20em; border: 1px solid #666;">
  <tr>
  <th>Value</th>
  <th>Timestamp</th>
  </tr>




  <tr *ngFor="let item of recordedData.Items">
  <td>{{item.Value}}</td>
  <td>{{item.Timestamp}}</td>
  </tr>
  </table>
  </div>
  <br />
  <br />
  <div *ngIf="getInt.value == true && interpolatedData && interpolatedData.Items">
  <h2 class="interpolated">Interpolated Values of Sinusoid</h2>
  <br />
  <table class="interpolated" border="0" style="width: 20em; border: 1px solid #666;">
  <tr>
  <th>Value</th>
  <th>Timestamp</th>
  </tr>
  <tr *ngFor="let item of interpolatedData.Items">
  <td>{{item.Value}}</td>
  <td>{{item.Timestamp}}</td>
  </tr>
  </table>
  <br /><br />
  </div>
</div>

 

Finally, we need to add the styles by editing the app.component.css file:

 

/* Page body */
body {
    font-family: Arial, Helvetica, sans-serif;
}
/* Definition lists */
dl {
    width: 100%;
    margin: 2em 0;
    padding: 0;
    clear: both;
    overflow: auto;
}


dt {
    width: 30%;
    float: left;
    margin: 0;
    padding: 5px 9.9% 5px 0;
    border-top: 1px solid #DDDDB7;
    font-weight: bold;
    overflow: auto;
    clear: left;
}


dd {
    width: 60%;
    float: left;
    margin: 0;
    padding: 6px 0 5px 0;
    border-top: 1px solid #DDDDB7;
    overflow: auto;
}
/* Headings */
h1 {
    font-weight: bold;
    margin: 35px 0 14px;
    color: #666;
    font-size: 1.5em;
}


h2 {
    font-weight: bold;
    margin: 30px 0 12px;
    color: #666;
    font-size: 1.3em;
}


h3 {
    font-weight: normal;
    margin: 30px 0 12px;
    color: #666;
    font-size: 1.2em;
}


h4 {
    font-weight: bold;
    margin: 25px 0 12px;
    color: #666;
    font-size: 1.0em;
}


h5 {
    font-weight: bold;
    margin: 25px 0 12px;
    color: #666;
    font-size: 0.9em;
}
/* Forms */
label {
    display: block;
    float: left;
    clear: both;
    text-align: right;
    margin: 0.6em 5px 0 0;
    width: 40%;
}


input, select, textarea {
    float: right;
    margin: 1em 0 0 0;
    width: 57%;
}


input {
    border: 1px solid #666;
}


    input[type=radio], input[type=checkbox], input[type=submit],
    input[type=reset], input[type=button], input[type=image] {
        width: auto;
    }

 

 

 

    Figure 1 - Sample application using Angular 2

 

Conclusions

 

Choosing the technology for your application is a really important step. You need to consider many factors including which technology your developer team is experienced and for how many years do you expect your app will become deprecated. Angular 2 is becoming really popular and it was developed to replace AngularJS. You can refer to this blog post in order to do the migration. If you are starting a new project, you are probably going to find a good reason to use Angular 2.

 

Please do not hesitate to share your comments below!

 

Finally, I would like to thank you for reading it!

Filter Blog

By date: By tag: