Skip navigation
All Places > PI Developers Club > Blog
1 2 3 Previous Next

PI Developers Club

523 posts



On the first 3 blog posts (part 1, part 2 and part 3) about developing the Google Maps PI Coresight custom symbol, 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:



Migrating from PI Coresight 2016 to PI Coresight 2016 R2


If you compare the source code between the version described in Part 3 with this new version, you will see some architectural changes due to the fact that part 3 was developed for PI Coresight 2016 and part 4 was developed for PI Coresight 2016 R2. Please refer to the PI-Coresight-2016-R2-(CTP)-Extensibility-Documentation  for more information about how to upgrade existing symbols from PI CS 2016 to 2016 R2.


One important change I want to point it out is the AngularJS directive for color picker. In 2016, this is how you would define on your HTML:


<format-color-picker id="markerColor" property="MarkerColor" config="config"></format-color-picker>


In 2016 R2, you would use:


<cs-color-picker id="markerColor" ng-model="config.MarkerColor"></cs-color-picker>




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


PI Coresight 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 Coresight infrastructure, otherwise, this symbol won't work on your PI Coresight 2016 R2.





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"
<div id="container" style="width:100%;height:calc(100% - 150px);">


<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)">

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


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) {



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.assetDatabaseGetByPath(scope.databasePath, null, null).then(function (response) {
                            var webId =;
                            piwebapi.assetDatabase.assetDatabaseGetEventFrames(webId, null, null, "*", null, 100, null, scope.elementName, "UserTemplate", true, null, null, null, null, null, null, "*-900d").then(function (response) {
                                scope.activitiesList =;
                                scope.selectedActivity =[0];


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)) {

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

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

                piwebapi.streamSet.streamSetGetInterpolatedAdHoc(webIds, activity.EndTime, null, true, "30s", null, activity.StartTime).then(function (response) {
                    for (var i = 0; i <; i++) {
                        var currentItem =[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 };
                            var point = new google.maps.LatLng(, pos.lng);
                    scope.rangeMax = routeCoordinates.length;

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


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

                    updateMarkerColor(scope.marker, scope.config);

                        polyfill: false,
                        onSlide: function (position, value) {
                            x = Math.round(position);
                            var geoPosition = routeCoordinates[x];






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.



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?




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.



Making the piwebapi service available on PI Vision 2016 R2


Yes, I am writing this procedure only for PI Vision 2016 R2 (or PI Coresight 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 piwebapi-kerberos.min.js (or piwebapi-kerberos.js) file to %PIHOME64%\Coresight\Scripts\app\editor folder.

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



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



3 - Edit the file located on the %PIHOME64%\Coresight\Scripts\app\editor by adding piwebapiClientLib 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', 'piwebapiClientLib'])


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-test.js on the %PIHOME64%\Coresight\Scripts\app\editor\symbols\ext folder with the following content:


(function (CS) {

  function symbolVis() { }

    var definition = {
        typeName: 'piwebapitest',
  datasourceBehavior: CS.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.home.homeGet({}).then(function(response) { =;



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


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



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.




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!



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


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 


Note: 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:




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:




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:




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){
                    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){
                    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.






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


Creating the PI Web API AngularJS Module


Create a new piwebapi-angularjs-service.js under the src folder with the following content:


'use strict'
angular.module('piwebapiClientLib',['base64']).factory('piwebapi', ['$http', '$httpParamSerializer', '$q','$base64',function ($http, $httpParamSerializer, $q, $base64) {

    //This factory method works like a service in which all HTTP requests are made.
    var serviceBase = '';

    var piwebapi = {};

    piwebapi.SetServiceBaseUrl = function(baseUrl) {
        serviceBase = baseUrl;

    piwebapi.SetKerberosAuth = function () {
        $http.defaults.withCredentials = true;
        $http.defaults.useXDomain = true;

    piwebapi.SetBasicAuth = function (username, password) {
        var auth = $base64.encode(username + ":" + password);
        $http.defaults.headers.common['Authorization'] = 'Basic ' + auth;


    piwebapi.CreateObjects = function() {
        piwebapi.analysis = new API.Client.AnalysisCategoryApi($http, $httpParamSerializer, serviceBase);
        piwebapi.analysisCategory = new API.Client.AnalysisCategoryApi($http, $httpParamSerializer, serviceBase);
        piwebapi.analysisRule = new API.Client.AnalysisRuleApi($http, $httpParamSerializer, serviceBase);
        piwebapi.analysisRulePlugIn = new API.Client.AnalysisRulePlugInApi($http, $httpParamSerializer, serviceBase);
        piwebapi.analysisTemplate = new API.Client.AnalysisTemplateApi($http, $httpParamSerializer, serviceBase);
        piwebapi.assetDatabase = new API.Client.AssetDataApiApi($http, $httpParamSerializer, serviceBase);
        piwebapi.assetServer = new API.Client.AssetServerApi($http, $httpParamSerializer, serviceBase);
        piwebapi.attribute = new API.Client.AttributeApi($http, $httpParamSerializer, serviceBase);
        piwebapi.attributeCategory = new API.Client.AttributeCategoryApi($http, $httpParamSerializer, serviceBase);
        piwebapi.attributeTemplate = new API.Client.AttributeTemplateApi($http, $httpParamSerializer, serviceBase);
        piwebapi.attributeTrait= new API.Client.AttributeTraitApi($http, $httpParamSerializer, serviceBase);
        piwebapi.batch = new API.Client.BatchApi($http, $httpParamSerializer, serviceBase);
        piwebapi.calculation= new API.Client.CalculationApi($http, $httpParamSerializer, serviceBase);
        piwebapi.configuration = new API.Client.ConfigurationApi($http, $httpParamSerializer, serviceBase);
        piwebapi.dataServer = new API.Client.DataServerApi($http, $httpParamSerializer, serviceBase);
        piwebapi.element = new API.Client.ElementApi($http, $httpParamSerializer, serviceBase);
        piwebapi.elementCategory= new API.Client.ElementCategoryApi($http, $httpParamSerializer, serviceBase);
        piwebapi.elementTemplate = new API.Client.ElementTemplateApi($http, $httpParamSerializer, serviceBase);
        piwebapi.enumerationSet = new API.Client.EnumerationSetApi($http, $httpParamSerializer, serviceBase);
        piwebapi.enumerationValue = new API.Client.EnumerationValueApi($http, $httpParamSerializer, serviceBase);
        piwebapi.eventFrame = new API.Client.EventFrameApi($http, $httpParamSerializer, serviceBase);
        piwebapi.home = new API.Client.HomeApi($http, $httpParamSerializer, serviceBase);
        piwebapi.point = new API.Client.PointApi($http, $httpParamSerializer, serviceBase);
        piwebapi.securityIdentity = new API.Client.SecurityIdentityApi($http, $httpParamSerializer, serviceBase);
        piwebapi.securityMapping = new API.Client.SecurityMappingApi($http, $httpParamSerializer, serviceBase); = new API.Client.StreamApi($http, $httpParamSerializer, serviceBase);
        piwebapi.streamSet = new API.Client.StreamSetApi($http, $httpParamSerializer, serviceBase);
        piwebapi.system = new API.Client.SystemApi($http, $httpParamSerializer, serviceBase);
        piwebapi.table = new API.Client.TableApi($http, $httpParamSerializer, serviceBase);
        piwebapi.tableCategory = new API.Client.TableCategoryApi($http, $httpParamSerializer, serviceBase);
        piwebapi.timeRule = new API.Client.TimeRuleApi($http, $httpParamSerializer, serviceBase);
        piwebapi.timeRulePlugIn = new API.Client.TimeRulePlugInApi($http, $httpParamSerializer, serviceBase);
        piwebapi.unit = new API.Client.UnitApi($http, $httpParamSerializer, serviceBase);
        piwebapi.unitClass = new API.Client.UnitClassApi($http, $httpParamSerializer, serviceBase);

    return piwebapi;





The code above creates a new AngularJS module called piwebapiClientLib 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


It 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). Finally, the CreateObjects methods, mapped the 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']);

gulp.task('concatenateAndMinify', function() {  
    return gulp.src(['src/API/Client/*.js', 'src/piwebapi-angularjs-service.js'])  

gulp.task('concatenate', function() {  
    return gulp.src(['src/API/Client/*.js', 'src/piwebapi-angularjs-service.js'])  



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


    "version": "0.1.0",
    "command": "gulp",
    "isShellCommand": true,
    "args": [
    "tasks": [ {
  "taskName": "default",
  "isBuildCommand": true,
  "showOutput": "always"



The code above will concatenate all JavaScript from src/API/Client and the src/piwebapi-angularjs-service.js to generate the 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 =;
            $scope.piServerExistsValue = true;
        }, function (error) {
            //this function will be executed in case of error
            $scope.piServerError =;
            $scope.piServerExistsValue = false;

        piWebApiHttpService.validPIPointName($scope.piServerName, $scope.piPointName).then(function (response) {
            $scope.piPointData =;
            $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 =;
            piWebApiHttpService.getSnapshotValue($scope.webId).then(function (response) {
                //Response of the snapshot is stored on the snapshotData
                $scope.snapshotData =;
            }, function (error) {
                $scope.snapshotError =;

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

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


The new app.js looks like:



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

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

    var result = piwebapi.home.homeGet({});

    // //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 = "PIFITNESS-SRV2";
        $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.dataServerGetByPath('\\\\' + $scope.piServerName).then(function (response) {
            //this function will be executed in case of success
            $scope.piServerData =;
            $scope.piServerExistsValue = true;
        }, function (error) {
            //this function will be executed in case of error
            $scope.piServerError =;
            $scope.piServerExistsValue = false;

        piwebapi.point.pointGetByPath('\\\\' + $scope.piServerName + '\\' + $scope.piPointName, null, null).then(function (response) {
            $scope.piPointData =;
            $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 =;
  $scope.webId).then(function (response) {
                //Response of the snapshot is stored on the snapshotData
                $scope.snapshotData =;
            }, function (error) {
                $scope.snapshotError =;

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

  $scope.webId, null, $scope.endTime, null,null, $scope.interval,  null, null, null, null,null, $scope.startTime).then(function (response) {
                $scope.interpolatedData =;
            }, function (error) {
                $scope.interpolatedError =;
        }, function (error) {
            $scope.piPointError =;
            $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!




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!

Starting in November 2016 until February 2017, we had the pleasure to have Miwa Teranishi a student at the University of New South Wales currently finishing her masters in Chemical/Biomedical Engineering.

I want to give here just the highlights of the internship.


The true highlight is the video she produced explaining her work:

Power Market Energy Price Forecasting Using Falkonry Pattern Recognition and the PI System



     As we have a history of working with the University of New South Wales, Miwa had heard of OSIsoft, but did not have a chance to use any of our tool. Thus, the first step was training. Fortunately, we have a battery of online training and she quickly mastered using our tools. Her training included various topic from:

     Creating Basic Reports with PI DataLink

     Building Basic Displays with PI ProcessBook

     Building Asset Hierarchies with PI AF

     Configuring Analytics with PI AF

Along with the VLE training available at Home Page - OSIsoft Learning

     Collecting Data using the new PI Connector for UFL

     Use Data Science for Machine Learning and Predictions Based on Your PI System Data


Data Collection:

     We were collecting information from public data sources, including: JEPX  and AEMO. This involved many INI files and python scripts. This work is currently being worked on so that it may be included in: GitHub - osisoft/PI-Connector-for-UFL-Samples: Documentation and supporting files to demonstrate usage of the PI Connect…



     We were now ready to start doing some analysis of the data we had collected. There are many tools that are available to us. Our requirements were:

          - Strong integration with PI

          - No programming required

          - Strong support (as we are not experts in machine learning)

     Falkonry excelled on all those points and they gladly allowed us to use their cloud service to run analyses on this data set and supported us as we did so. This part of the story is well explained in the video above. I hope you find it interesting as we did!


     Falkonry also participated at our Users Conference 2017 held in San Francisco in March this year. One of the highlights of their participation was this presentation held by Ciner,

It's seems AFTime is one of my favorite blog topics.  I suppose you could consider this blog a part of a series ... that was started 5 years ago!  Here were my earlier posts:


My first blog - It's About Time!  (a discussion about sub-second times)

A Detailed Exploration of AFTime Precision


At UC SF 2017, I met lots of people and engaged in interesting discussions.  There were 2 notable questions about time.  One from a student in a lab, and the other from a work colleague.  To reword their questions:


  1. When I create an AFTime, sometimes it's Utc and sometimes it Local.  How can I know which is which ahead of time?
  2. I am trying to write a custom method to parse a relative time string.  Do you have any tips on the best way to do this?


On the surface, these 2 questions seem to only be related to AFTime in general.  But actually the same answer applies to both as it depends on the same solution: the AFTime constructor. I thought that the first question has been answered a dozen times before.  Apparently it must be answered again. 


There is a general rule of thumb that goes:


DateTimes are UTC; Strings are Local


Overall it's a decent rule of thumb, but not exactly correct. There are some finer details and exceptions to understand.  Before we jump right to the straight-forward solutions, let's take a brief sidetrack to have a better understanding of the objects involved.


Differences between AFTime and DateTime

If you ask 10 different developers to describe the difference between AFTime and DateTime, you would get 10 different replies.  For the topic being discussed, there are 3 relevant differences that I choose to discuss.


The first pertains to DateTime's Kind property and its notion of Utc, Local, and Unspecified.  While AFTime doesn't have a Kind property, it is quite aware of UtcTime and LocalTime but most importantly does not support any notion of Unspecified.  What this means is that if you pass anything to AFTime that falls into the Unspecified category, then AFTime must decide what to do.  It could reject it by throwing an exception, but it doesn't.  Instead AFTime makes an assumption of how unspecified time zones should be treated, yet this treatment varies depending upon the type of object being passed to AFTime.


In short, DateTime objects passed to AFTime will treat Unspecified as Utc, and String objects lacking time zone info will be treated as Local.


The second key difference between AFTime and DateTime is the MinValue for each.  While they both share the same MaxValue, AFTime.MinValue is 1/1/1970 whereas DateTime.MinValue is 1/1/1601.  This coupled with the above means there is some validation performed on the time that is input to AFTime.  That is to say at the very least the input time may be clamped to 1/1/1970 on the low end.


The 3rd critical difference is that while DateTime.Parse allows for a typical time string to be passed in, AFTime supports relative time formats such as "*-8h".


Constructors always using Utc

There are 2 AFTime constructors that will have input that is always UTC-based.

AFTime Constructor (Double)

AFTime Constructor (Int64)


The Double overload will accept seconds for a UTC-based DateTime.  The Int64 will accept ticks for a UTC-based DateTime.  Using either of these 2 constructors always expect the input value to be UTC-based.  It is YOUR responsibility to make sure the Double or Int64 you pass in is also UTC-based.  If you have code that isn't working correctly, look for a bug in YOUR application.


This code has a hard-to-find bug:

DateTime dTime = DateTime.Now;
AFTime aTime = new AFTime(dTime.Ticks);


Because dTime is Local but AFTime was expecting something UTC-based.  The bug is on your end and the easy fix is up to you:


AFTime aTime = new AFTime(dTime.ToUniversalTime().Ticks);



DateTime does allow Local

AFTime Constructor (Double)


The short rule of thumb "DateTimes are UTC" isn't totally correct in that you may pass a DateTime input with a Local DateTimeKind.


To clarify, a DateTime input to AFTime is not always treated as Utc.  If a DateTime with Kind of Local is passed in, then that Local time is honored as one would expect.  The only fuzzy area where there is a maybe is if a DateTime with Unspecified is passed in.  For those cases, the Kind is assumed to be Utc.  In other words, the Kind is simply changed to Utc and not converted to Utc.  You may think of this pseudo-code as happening when you pass a DateTime to AFTime:


// Unspecified is changed to Utc
// Local is converted to Utc
if (time.Kind == DateTimeKind.Unspecified)
    time = DateTime.SpecifyKind(time, DateTimeKind.Utc);
else if (time.Kind == DateTimeKind.Local)
    time = time.ToUniversalTime();

// Actually more complex than this because dates before 1900 throw an exception.
if (time < AFTime.MinValue.UtcTime)
    time = AFTime.MinValue;

// Internally time is used because its UTC-based and clamped to 1/1/1970


That's not the actual code that runs in the AFTime constructor, but it's appropriate example to understand some of its inner workings.  The internal value is stored in a DateTime object with Kind of Utc and clamped on the low end to 1/1/1970 (AFTime.MinValue).


Bottom line: if the DateTime specifies Utc or Local, it will be honored.  If the Kind is Unspecified, it is considered to be Utc.


String does allow UTC, time zones, and time zone offsets

AFTime Constructor (String)


A String input to AFTime will be treated as Local unless there is time zone specified or time zone offset information contained within the string.  If you include it, it will be honored.  If you omit it, it's treated as Local.  Just as you should expect.


So these time strings would be Local times as they lack any time zone offset:

"2017-04-03 16:25:00"



Whereas these time strings would be set to UTC-based times:




It's nice to know that the last 2 time strings would easily be parsed by DateTime.Parse.  Whether the returned DateTime is Utc or Local, the same AFTime logic in the previous section applies.  This answers the first question I received at UC: When I create an AFTime, sometimes it's Utc and sometimes it Local.  How can I know which is which ahead of time?


Relative Time Format strings

See the Remarks for AFTime Constructor (String)


Notice that our string examples looked like typical date and time strings.  A critical difference that AFTime has over DateTime is its support of Relative Time Format strings.  Relative Time Format strings are Local based.  This should not be surprising given that concepts as Today and Yesterday are Local in context.  My Today in Houston is very different from someone else's Today in Europe.


For my colleague,  Thyagarajan Ramachandran also known as Thyag,  who said "I am trying to write a custom method to parse a relative time string.  Do you have any tips on the best way to do this?"  My best advice was: Don't.  There is no need to reinvent-the-wheel to parse a relative time format string yourself.  The  AFTime Constructor (String) already does this.  Save yourself lots of coding and debugging time, and instead rely on AFTime.  To think of it another way, if your goal is to code a method that parses a relative time string in a manner that is compatible with AFTime, why not just let AFTime perform all the heavy lifting because in the simplest of wisdom: whatever AFTime does is 100% compatible with AFTime!


I mentioned to Thyag the fact that AFTime supports multiple relative time formats such as "t+5h-30m" to have my local Today at 4:30 AM.  This initially surprised him and he admitted his method did not take this into account.  That news immediately means his custom method was not fully compatible with AFTime's parsing capabilities. Sometimes reinventing-the-wheel is not such an easy task!


To make your head spin even more, at the bottom of the AFTime Constructor (String) help is a quite curious example of "Sat, 01 Nov 2008 19:35:00 GMT + 2y+5d-12h+30.55s" to fully demonstrate relative time formats. Note only can you have multiple relative time intervals, it can accept floating point values (see 30.55s).


There are also some interesting corner cases with AFTime and input time strings that Thyagarajan Ramachandran (Thyag) discovered.


For example, new AFTime() returns AFTime.MinValue.


Whereas, new AFTime(null) or new AFTime(timestring) where timestring is null or empty returns AFTime.Now.


A time string of "-12" is equivalent to "*-12h".  That is to say if the initial time portion is omitted, it is assumed to be "*".  Likewise if time units are omitted, it is assumed to be "h".


There's also another assumption for something omitted though it's not apparent in the previous examples.  If there is no number specified, it is assumed to be zero.  This leads to the quite curious but very valid instances of:


new AFTime("-") is same as "*-0h"

new AFTime("+") is same as "*+0h"


Again, both instances are quite legal and would return AFTime.Now.


I would say that sufficiently answers the 2nd question I heard at UC SF 2017.  If you have anymore regarding AFTime, drop me a line.

Data and community are two major assets that cannot be invented or disrupted as quickly as technology. At the OSIsoft Users Conference 2017 in San Francisco I had the opportunity to witness firsthand how the PI community is pushing the envelope for a smarter manufacturing world. In the process they have at their disposal decades of real data sitting across several thousands of PI Systems around the world. An ever-increasing number of organizations are making good on the promise of turning time-series process data into decision-ready and predictive knowledge. Below is a short summary of my observations.


Activities and opportunities


  • Hackathon: we offered real operational data from Barrick Gold, the biggest gold producer in the world. The dataset spanned 6 months of sensor measurements from their massive haul trucks. 60 data streams from 30+ trucks made this a rich dataset. The trucks are big parts of Barrick’s operational cost. Each of these giants cost $4MM while one tire costs $50K. Their miles per gallon (mpg) is 0.3. That gives you an idea how costly this operation is and how efficiency can be vital to any mine operator like Barrick. When a truck is full the value of gold waiting to be extracted is $60K. It means if a truck goes down without notice a significant amount of capital can sit in the middle of nowhere for days before fix arrives.

Our hackers took advantage of the opportunity and built very innovative ideas. As a judge I was struck by the level of quality and maturity of the submissions in 23 hours. Several teams focused on advanced analytics and data as the cornerstone of their submission while others focused on software development. Most notably, the winning team merged machine learning with social engineering to design a system where drivers would earn points by driving “well”. And “well” was learned through sifting through sensor data, the strains and temperatures across the truck body, as well as geospatial qualities of the road.


  • Partners and customers: several OSIsoft partners and customers flexed their muscles around data science with PI data. We enjoyed several hands-on labs and presentations on the topic ranging from real customer stories to educational pieces on how to pull off a successful machine learning project with PI data. Most notably “United Utilities” (UK) presented how they built a demand forecasting engine which is critical to serving water to their customers efficiently. “Total” showcased how they use data form PI System to build and deploy a model and predict the percentage of gasoil in the residues of their distillation tower. Many conversations I had with attendees all point to the acceleration of more advanced analytics and data science in the PI world. This is all exciting because it shows how much business opportunity is out there waiting to be tapped.




Like everything else in the world the nice benefits don’t come for free. There are still significant challenges along the way:


  • Quality of the data: throughout the event a constant theme was challenges around data quality. While it’s typical to immediately focus on the machine learning algorithm or architecture it is evident that industrial data can be messy, vague, or flat out nonsense. The nature of these data sources and their paths to server make them susceptible to sensor error, noise contamination, process errors, mistakes in units of measure, unlogged changes over time, and lack of context to name a few. A significant amount of effort has to be spent on vetting, reshaping, cleaning, and reformatting data before a machine learning algorithm can be applied. Building a diverse and broad team of data scientists and subject matter experts seem to be the right strategy to alleviate such pain points.
  • Cultural and governance issues: preserving and sharing data may not be as easy as the technology that enables it. The industrial community has a long history of protecting itself against all sorts of malicious attacks and innocent mistakes, hence isolating itself from the rest of the world. The new needs and opportunities call for easing up some of the traditional requirements while guaranteeing security. It takes a significant cultural shift on top of technological and security advancements. Besides, addressing the data quality issues mentioned above takes a change in the mindset across the organization from top to bottom. To top this off with yet another layer, in many cases data is comprised of elements sitting in different jurisdictions which makes data sharing and aggregation even more challenging.


The opportunities for data science and machine learning in the PI world abound as do the challenges. However, challenges are nothing that we can’t overcome with the power of our smart and energetic community. All the signs are pointing to a wave of industrial organizations investing serious capital and resources in this area. After all it may be the differentiator between the survivors and failures of the coming decade. I ask of anyone in this community to share their thoughts, experiences, challenges, and ideas in this field. We at OSIsoft are committed to push this forward with your help.

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.


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!



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.




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





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.





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.




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!



It's that time of year again!  In recognition of an ever growing community, we have added a new category this year called Rising Star in order to shine the spotlight on a few more, well-deserving contributors. The goal is to acknowledge the great impact of newer faces in our community. If you have thoughts on this new category or ideas regarding possible future categories, please share them with us.


As a former customer and All-Star myself, it is my distinct pleasure to announce the 2017 All-Stars and Rising Stars.  From unselfishly dedicating their time to answer posts by others, as well as participating in interesting discussions to expand our overall knowledge base, these individuals are a tremendous source of valued experience within the PI community.  We would not be where we are without the many great contributions from these individuals.  So I ask my fellow PI Geeks for a hearty round of applause as we recognize these respected members of our community.


PI Developers Club Community All-Stars 2017


They win:

  1. Recognition in the community
  2. One year free subscription or renewal to PI Developers Club
  3. One-time free registration at UC/TechCon (UC EMEA 2017 or UC San Francisco 2018 - not including labs)
  4. Amazon gift card worth $400 USD


PI Developers Club Community Rising Stars 2017


They win:

  1. Recognition in the community
  2. One year free subscription or renewal to PI Developers Club
  3. One-time free registration at UC/TechCon (UC EMEA 2017 or UC San Francisco 2018 - not including labs)
  4. Amazon gift card worth $400 USD


PI Developers Club OSIsoft All-Stars 2017


They win:

  1. Recognition in the community
  2. Amazon gift card worth $200 USD


Please join me in congratulating our 2017 All-Stars!  Will you be among the nominees next year?

Hey PI geeks,


All of us on the PI DevClub Team are very excited about the upcoming UC 2017 in San Francisco! We are looking forward to chatting with all of you and hearing what you've been up to with PI Developer Technologies.We've been working on some things too and would love to share them with you!


Until then, here's a sneak peek at two of the things we're experimenting with:


Search-based Asset Context Picker for PI Coresight Displays


This widget gives an alternative way of picking assets of interest in PI Coresight, starting with some explicit search fields and allowing the results of multiple searches to be combined. It's still a work in progress - Paul Martin, Robert Schmitz, and I are hoping this can drive some good discussions.

Custom Search-based Asset Context Picker for Coresight Displays - YouTube

GitHub repository: PI-Coresight-Custom-Symbols/Community Samples/CoolTeam at master · osipmartin/PI-Coresight-Custom-Symbols · GitHub



Custom Symbol for Manual Data Entry

Manual Data Entry symbol for PI Coresight - YouTube

GitHub repository: PI-Coresight-Custom-Symbols/Community Samples/OSIsoft/manual-data-entry at master · AnnaPerry/PI-Coresight-Custom-Symbol…

Also available in single-button form:


If these are interesting to you, or you have some ideas and suggestions, we hope you'll come see us at the PI Developers Club pod at the Users Conference!

UC San Francisco 2017 will offer a new series of major activities and content for the builders: PI System developers, data scientists, and architects. With a hackathon and 1.5 days of packed activity there will be a lot to take home. To provide added focus all of these activities will happen at Parc55 hotel which is separate but just next to the main conference venue. Here is a high level overview of the activities:


  • Hackathon – Monday-Tuesday
    • Barrick Gold will be our data sponsor and the theme is IIoT. Our sponsor offers real and rich datasets coming from their massive haul trucks
  • Developer Track – Wednesday afternoon and all Thursday
    • 10 unique hands-on labs covering novice to expert personas on a multitude of PI System and data science concepts
    • 11 technical presentations by customer, partner, and OSIsoft presenters
    • Focus on data science with a lab and 3 presentations
    • Developer Lounge for PI Geeks to stop by product booths and geek it out
    • Vox Pop sessions for ad hoc roundtable discussions whose topics are decided by the voice of attendees
    • General session with a developer-centric roadmap talk
    • Award ceremony and Geek Night


I encourage all PI Geeks to take advantage of this opportunity.

I checked PI Coresight Value symbol and found that the font-family is Arial by default.

I used Google Chrome + F12 key to found out.

However, sometimes we want to change font-family like following.

How to achieve it?

We can change the value symbol code to achieve it.

For doing this change, please take a back up of the original files.

Please change these files by your responsibilities.


At first, we can add font-family pull down to the config file. (The top of the file code)

C:\Program Files\PIPC\Coresight\Scripts\app\editor\symbols\sym-value-config.html

<div class="c-side-pane t-toolbar">
 <span style="color:#fff; margin-left:10px;">Font</span>
<div class="config-option-format">
<select ng-model="config.fontFamily">
 <option style="font-family: Georgia">Georgia</option>
 <option style="font-family: 'Times New Roman'">Times New Roman</option>
 <option style="font-family: Arial">Arial</option>
 <option style="font-family: Impact">Impact</option>
 <option style="font-family: Tahoma">Tahoma</option>
 <option style="font-family: 'Trebuchet MS'">Trebuchet MS</option>
 <option style="font-family: Verdana">Verdana</option>
 <option style="font-family: 'Courier New'">Courier New</option>
 <option style="font-family: 'Lucida Console'">Lucida Console</option>

We use config.fontFamily object to get the font-family name. It shows following.

For changing the visibility we need to change template file.

C:\Program Files\PIPC\Coresight\Scripts\app\editor\symbols\sym-value-template.html

We just need to add 'font-family':config.fontFamily to the ng-style of value-symbol-portionn-text div.

<div class="value-symbol-portion-text" style="white-space:nowrap;text-align:left;overflow:hidden;" ng-style="{background: (Fill || config.Fill), 'font-size': fontSize, 'font-family':config.fontFamily}" ng-class="{'blink': Blink}">

Also it is good to add default value to the .js file.

C:\Program Files\PIPC\Coresight\Scripts\app\editor\symbols\coresight.sym-value.js

getDefaultConfig: function () {
var config = CS.SymValueLabelOptions.getDefaultConfig({
                DataShape: 'Value',
                Height: 60,
                Fill: 'rgba(255,255,255,0)',
                Stroke: 'rgba(119,136,153,1)',
                ValueStroke: 'rgba(255,255,255,1)',
                ShowTime: true,
                IndicatorFillUp: 'white',
                IndicatorFillDown: 'white',
                IndicatorFillNeutral: 'gray',
                ShowDifferential: true,
                DifferentialType: 'percent',
                ShowIndicator: false,
                ShowValue: true,
                ShowTarget: true,
                fontFamily : 'Arial'

You can enjoy a lot of fonts.

Adding, you can add your fonts to .config file!!

I hope PI Coresight in future version support settings for font-family too.

It contains google fonts and by default, windows machine does not contain these.

We can click plus icon on the page to use the font. Can select multiple items.

You can copy the link to template file.

C:\Program Files\PIPC\Coresight\Scripts\app\editor\symbols\sym-value-template.html

Add the link to the top of the file with <head> to show the fonts.

<link href="" rel="stylesheet">

You need to add the font name to config file!

 <option style="font-family: 'Dancing Script'">Dancing Script</option>


Now I can use Dancing Script font.

You can do the same thing to plain text too.


The same as previous.


sym-statictext-template.html (Added 'font-family':config.fontFamily)

 <div class="static-text-symbol"
 ng-style="{background: (Fill || config.Fill), 'font-size': fontSize}"
 ng-class="{'blink': Blink}">
 <a ng-href="{{linkURL}}" ng-attr-target="{{config.NewTab ? '_blank': '_self'}}" rel="noreferrer">
 <span class="text-symbol-sizing-line"
                  ng-class="{link: config.LinkURL}"
                  style="font-family:Arial, Helvetica, sans-serif;display: inline-block;"
 ng-style="{color:config.Stroke, 'font-family':config.fontFamily}"


Now I can see text with font-family too.

Some font shows bigger size than value symbol space. So we need to change the value symbol size for some specific fonts though. I have not done yet.

I worked with Jerome Lefebvre about the Google fonts. Thank you Jerome!!

I occasionally see people in the Developer's Club requesting information on how to get PI SDK logs (such as information from their PI Data Archive) using the PI AF SDK.  The AF SDK does not provide this functionality and often our go-to response is to use the PowerShell Tools for the PI System. It's a great product but I can understand not wanting to learn how to use Powershell to do something simple such as getting message logs.


To make things easier, I decided to write a quick C# wrapper so that you can leverage these tools without leaving the comfort of .NET .   This is by no means an official solution.  Just something I thought might be helpful.

You can download the project here: GitHub - osipmartin/PowershellLogWrapper: C# Wrapper for Powershell Get-PIMessage to get PI SDK logs.


There are two projects in the solution.

Powershell_Logging: This is the actual library.  When you build the project, it will produce a dll that you can use elsewhere.

ReadPSLog: This is a simple console application that shows how to use one of the functions from the library.


I did just a bare minimum of testing.  If you have issues please feel free to tell me or fork the repository and fix it.


Note: I included the switches that I use regularly when I am getting messages but left out a couple switches that I never use.  Feel free to fork and modify to your hearts content.

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=""
        android:layout_height="wrap_content" />
        android:layout_height="wrap_content" />


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




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!



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

  declarations: [
  imports: [
  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";

export class PIWebAPIService {
    private piWebApiBaseUrl = '';  // 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;

  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
      .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 =;
      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>
  Please fill in your details below and click  on &quot;Get PI Data&quot;. Fields marked with an asterisk (*) are required.
  <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>
  <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>

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

  <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 *ngIf="requestMode==false">
  <h1>Displaying SINUSOID data</h1>

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

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

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

  <div id="errors">
  <div *ngIf="snapshotError && snapshotError.Errors">
  <p *ngFor="let error of snapshotError.Errors">{{error}}</p>
  <div *ngIf="recordedError && recordedError.Errors">
  <p *ngFor="let error of recordedError.Errors">{{error}}</p>
  <div *ngIf="interpolatedError && interpolatedError.Errors">
  <p *ngFor="let error of interpolatedError.Errors">{{error}}</p>
  <div *ngIf="piServerError && piServerError.Errors">
  <p *ngFor="let error of piServerError.Errors">{{error}}</p>
  <div *ngIf="piPointError && piPointError.Errors">
  <p *ngFor="let error of piPointError.Errors">{{error}}</p>
  <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 *ngIf="snapshotData">
  <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 *ngFor="let item of recordedData.Items">
  <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 *ngFor="let item of interpolatedData.Items">
  <br /><br />


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




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: