Skip navigation
All Places > PI Developers Club > Blog > 2016 > April
2016

Introduction

 

On this blog post, we will show you samples about how to use PI Web API Batch and Channels in C#. They both are some of the new features (in CTP) of PI Web API 2015 R3, Nevertheless, on the upcoming PI Web API 2016 release, the Batch service will be merged into the core services.

 

Please refer to this GitHub repository which was created to store the source code package of this blog post.

 

We will create a console application with the JSON.NET library, which can be downloaded using NuGet Package Manager.

 

The old way to make multiple requests with Tasks

 

First, let's remember how we used to run multiple HTTP requests simultaneously on previous version of the product. Please refer the code snippet below which shows how to create 50 new PI Points using C# Tasks.

 

     public static void Start()
        {
            int piPointsToBeCreated = 50;


            List<string> piPointNames = new List<string>();
            for (int i = 0; i < piPointsToBeCreated; i++)
            {
                piPointNames.Add("PIWebAPITest" + i.ToString());
            }
            
            Task<int>[] tasks = new Task<int>[piPointNames.Count];


            for (int i = 0; i < piPointNames.Count; i++)
            {
                tasks[i] = new Task<int>((piPointName) =>
                {
                    return CreatePIPoint(piPointName.ToString());
                }, piPointNames[i]);
            }


            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
            // start the antecedent tasks
            foreach (Task t in tasks)
            {
                t.Start();
            }


            Task.WaitAll(tasks);
            stopWatch.Stop();
            Console.WriteLine("Time elapsed: " + stopWatch.ElapsedMilliseconds);



            Console.WriteLine("Press enter to finish");
            Console.ReadLine();
        }




        internal static int CreatePIPoint(string piPointName)
        {
            string url = "https://marc-web-sql.marc.net/piwebapi/dataservers/s0IQnVxJzj6UuohiErVnqUqgTUFSQy1QSTIwMTQ/points";
            dynamic postBody = new JObject();
            postBody.Name = piPointName;
            postBody.PointClass = "classic";
            postBody.PointType = "Float32";
            postBody.Future = false;


            WebRequest request = WebRequest.Create(url);


            request.Credentials = new NetworkCredential("username", "password");


            ((HttpWebRequest)request).UserAgent = ".NET Framework Example Client";


            request.Method = "POST";
            request.ContentType = "application/json";


            byte[] byteArray = Encoding.UTF8.GetBytes(postBody.ToString());
            request.ContentLength = byteArray.Length;
            Stream dataStream = request.GetRequestStream();
            dataStream.Write(byteArray, 0, byteArray.Length);
            dataStream.Close();


            WebResponse response = request.GetResponse();
            return Convert.ToInt32(((System.Net.HttpWebResponse)(response)).StatusCode);
        }

 

By viewing the console application, it was needed 3.8 seconds for the 50 tags to be created.

 

 

You can find more information about this technique by reading my previous blog post: PI Web API 2015 R2 - New features demo - C#

 

The new way to make multiple requests with PI Web API Batch

 

The Batch controller has only one Action called Execute. The description below was copied from the PI Web API Online help:

 

 

Execute a batch of requests against the service. As shown in the Sample Request, the input is a dictionary with IDs as keys and request objects as values. Each request object specifies the HTTP method and the resource and, optionally, the content and a list of parent IDs. The list of parent IDs specifies which other requests must complete before the given request will be executed. The example first creates an element, then gets the element by the response's Location header, then creates an attribute for the element. Note that the resource can be an absolute URL or a JsonPath that references the response to the parent request. The batch's response is a dictionary uses keys corresponding those provided in the request, with response objects containing a status code, response headers, and the response body.

 

 

The idea of BATCH is to make a single HTTP request that will actually execute many requests on the server side.  This can be achieved as the body request is an array of objects, each one has some properties to define as Method, Resource and Content which represents a single HTTP request. In order to create 50 PI Points, the body message is an array with 50 objects, each one will create a specific PI Point. Please refer to the example below:

 

 

        public static void Start()
        {
            int piPointsToBeCreated = 50;


            List<string> piPointNames = new List<string>();
            for (int i = 0; i < piPointsToBeCreated; i++)
            {
                piPointNames.Add("PIWebAPITest" + i.ToString());
            }


            dynamic globalBatch = new JObject();
            for (int i = 0; i < piPointNames.Count; i++)
            {
                dynamic postCreatePoint = new JObject();
                postCreatePoint.Name = piPointNames[i];
                postCreatePoint.PointClass = "classic";
                postCreatePoint.PointType = "Float32";
                postCreatePoint.Future = false;
      


                dynamic batchCreatePoint = new JObject();
                batchCreatePoint.Method = "POST";
                batchCreatePoint.Resource = "https://marc-web-sql.marc.net/piwebapi/dataservers/s0IQnVxJzj6UuohiErVnqUqgTUFSQy1QSTIwMTQ/points";
                batchCreatePoint.Content = postCreatePoint.ToString();
                globalBatch[(i+1).ToString()] = batchCreatePoint;
            }
            Console.WriteLine(globalBatch.ToString());
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
      
            Task<int> task = Task<int>.Factory.StartNew(() =>
            {
                return SendBatchRequest(globalBatch);
            });




            task.Wait();
            stopWatch.Stop();
            Console.WriteLine("Time elapsed: " + stopWatch.ElapsedMilliseconds);


            Console.WriteLine("Press enter to finish");
            Console.ReadLine();
        }




        internal static int SendBatchRequest(dynamic postBatch)
        {
            string url = "https://marc-web-sql.marc.net/piwebapi/batch";




            WebRequest request = WebRequest.Create(url);


            request.Credentials =  new NetworkCredential("username", "password");


            ((HttpWebRequest)request).UserAgent = ".NET Framework Example Client";


            request.Method = "POST";
            request.ContentType = "application/json";


            byte[] byteArray = Encoding.UTF8.GetBytes(postBatch.ToString());
            request.ContentLength = byteArray.Length;
            Stream dataStream = request.GetRequestStream();
            dataStream.Write(byteArray, 0, byteArray.Length);
            dataStream.Close();


            WebResponse response = request.GetResponse();
            return Convert.ToInt32(((System.Net.HttpWebResponse)(response)).StatusCode);
        }

 

By viewing the console application, it was needed only 1.2 seconds (31% of 3.8s) for the 50 tags to be created using BATCH.

 

 

There is no doubt about the performance improvement that your app can benefit using this service.

 

 

Chaining requests with Batch

 

The previous example has shown how to use BATCH with independent requests as no response was used as an input to generate the resource for another request. Nevertheless, this is not always true. Imagine a scenario with two requests where:

 

  1. The first request gets the attributes information from the sinusoid PI Point of the MARC-PI2014 PI Data Archive, including the WebId
  2. The second request uses the WebId to generate a new Resource (link) in order to retrieve recorded values of the SINUSOID.

 

The previous example, the response of the first request is used to generate the url of the second request. This is called Chaining Requests.

 

Let's take a look at how this works in C#. Please refer to the code snippet below:

 

 

        public static void Start()
        {


            dynamic globalBatch = new JObject();
            globalBatch["1"] = new JObject();
            globalBatch["1"].Method = "GET";
            globalBatch["1"].Resource = @"https://marc-web-sql.marc.net/piwebapi/points?path=\\marc-pi2014\sinusoid";
            globalBatch["1"].Headers = new JObject();
            globalBatch["1"].Headers["Cache-Control"] = "no-cache";




            globalBatch["2"] = new JObject();
            globalBatch["2"].Method = "GET";
            globalBatch["2"].Resource = "https://marc-web-sql.marc.net/piwebapi/streamsets/recorded?webId={0}";
            globalBatch["2"].Parameters = new JArray(new object[] { "$.1.Content.WebId" });
            globalBatch["2"].ParentIds = new JArray(new object[] { "1" });


            Console.WriteLine(globalBatch.ToString());
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();


            dynamic responseObject = SendBatchRequest(globalBatch);


            stopWatch.Stop();
            Console.WriteLine("SYNC: Time elapsed: " + stopWatch.ElapsedMilliseconds);


        }




        internal static dynamic SendBatchRequest(dynamic postBatch)
        {
            string url = "https://marc-web-sql.marc.net/piwebapi/batch";
            WebRequest request = WebRequest.Create(url);
            request.Credentials = new NetworkCredential("username", "password");
            ((HttpWebRequest)request).UserAgent = ".NET Framework Example Client";
            request.Method = "POST";
            request.ContentType = "application/json";
            byte[] byteArray = Encoding.UTF8.GetBytes(postBatch.ToString());
            request.ContentLength = byteArray.Length;
            Stream dataStream = request.GetRequestStream();
            dataStream.Write(byteArray, 0, byteArray.Length);
            dataStream.Close();
            string result = string.Empty;
            WebResponse response = request.GetResponse();
            using (var reader = new StreamReader(response.GetResponseStream()))
            {
                result = reader.ReadToEnd();
                //Console.WriteLine(result);
            }
            return JsonConvert.DeserializeObject<dynamic>(result);
        }

 

Note that:

  • ParentsId = ["1"] on the second request was used to make PI Web API wait for the first request to finish before starting the second request.
  • The Resource property of the second request used a parameter {0} on the link defined on the Parameter property.
  • The Parameter property has an array with only one element which is $.1.Content.WebId. This means that the first parameter of the second Resource is the WebId of the response of the first request.

 

 

Running async

 

Let's improve our code by making our program run async methods.

 

        public async static Task StartAsync()
        {


            dynamic globalBatch = new JObject();
            globalBatch["1"] = new JObject();
            globalBatch["1"].Method = "GET";
            globalBatch["1"].Resource = @"https://marc-web-sql.marc.net/piwebapi/points?path=\\marc-pi2014\sinusoid";
            globalBatch["1"].Headers = new JObject();
            globalBatch["1"].Headers["Cache-Control"] = "no-cache";




            globalBatch["2"] = new JObject();
            globalBatch["2"].Method = "GET";
            globalBatch["2"].Resource = "https://marc-web-sql.marc.net/piwebapi/streamsets/recorded?webId={0}";
            globalBatch["2"].Parameters = new JArray(new object[] { "$.1.Content.WebId" });
            globalBatch["2"].ParentIds = new JArray(new object[] { "1" });


            Console.WriteLine(globalBatch.ToString());
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();


            dynamic responseObject = await SendBatchRequestAsync(globalBatch);


            stopWatch.Stop();
            Console.WriteLine("ASYNC: Time elapsed: " + stopWatch.ElapsedMilliseconds);


        }








        internal async static Task<dynamic> SendBatchRequestAsync(dynamic postBatch)
        {
            string url = "https://marc-web-sql.marc.net/piwebapi/batch";




            WebRequest request = WebRequest.Create(url);


            request.Credentials = new NetworkCredential("marc.adm", "kk");


            ((HttpWebRequest)request).UserAgent = ".NET Framework Example Client";


            request.Method = "POST";
            request.ContentType = "application/json";


            byte[] byteArray = Encoding.UTF8.GetBytes(postBatch.ToString());
            request.ContentLength = byteArray.Length;
            Stream dataStream = request.GetRequestStream();
            dataStream.Write(byteArray, 0, byteArray.Length);
            dataStream.Close();
            string result = string.Empty;
            WebResponse response = await request.GetResponseAsync();
            using (var reader = new StreamReader(response.GetResponseStream()))
            {
                result = reader.ReadToEnd();
                //Console.WriteLine(result);
            }
            return JsonConvert.DeserializeObject<dynamic>(result);
        }

 

The main concepts to convert those methods are:

  • Convert methods signature:
    • static void --> async static Task
    • static dynamic --> async static Task<dynamic>
  • Use async methods if possible:
    • request.GetResponse() --> request.GetResponseAsync()
  • Add await for async methods
    • request.GetResponseAsync() --> await request.GetResponseAsync()
    • sendBatchRequestAsync() --> await sendBatchRequestAsync()

 

Finally, we just need to change the Main method from Program.cs in order to be able to make async requests as the Main method runs syncronous.

 


            Task.Run(async () =>
            {
                await ComplexBatchTest.StartAsync();
            }).Wait();

 

 

 

Using Channels (CTP) in C#

 

Under Topics of the PI Web API online help, there is one for Channels. There you can find useful information about how to use this funcionality.

 

By using the WebId of the sinusoid PI Point, I have generated the following URI:

 

wss://marc-web-sql.marc.net/piwebapi/streams/P0IQnVxJzj6UuohiErVnqUqgAQAAAATUFSQy1QSTIwMTRcU0lOVVNPSUQ/channel

 

The first step is to make sure channels is working properly by accessing a pre-built tool available at https://www.websocket.org/echo.htmlIf you are using Basic authentication, you might receive 401 errors after making the HTTP requests. In this case, open another tab on the browser, access PI Web API main endpoint, type your credentials, refresh the original web page and try again.

 

If it is working fine, let's try to receive new updates from the sinusoid PI Point in C#. I have referred to this stackoverflow thread to get started.

 

Please review the code snippet below. I have copied the URI on the code itself to make things easier.

 


        public static async Task Client()
        {


            ClientWebSocket ws = new ClientWebSocket();
            ws.Options.SetRequestHeader("Authorization", "Basic xxxxxxxxxx");


            var uri = new Uri("wss://marc-web-sql.marc.net/piwebapi/streams/P0IQnVxJzj6UuohiErVnqUqgAQAAAATUFSQy1QSTIwMTRcU0lOVVNPSUQ/channel");
            try
            {
                await ws.ConnectAsync(uri, CancellationToken.None);
            }
            catch (Exception)
            {
        
            }


            var buffer = new byte[1024];
            while (true)
            {
                var segment = new ArraySegment<byte>(buffer);


                var result = await ws.ReceiveAsync(segment, CancellationToken.None);


                if (result.MessageType == WebSocketMessageType.Close)
                {
                    await ws.CloseAsync(WebSocketCloseStatus.InvalidMessageType, "I don't do binary", CancellationToken.None);
                    return;
                }


                int count = result.Count;
                while (!result.EndOfMessage)
                {
                    if (count >= buffer.Length)
                    {
                        await ws.CloseAsync(WebSocketCloseStatus.InvalidPayloadData, "That's too long", CancellationToken.None);
                        return;
                    }


                    segment = new ArraySegment<byte>(buffer, count, buffer.Length - count);
                    result = await ws.ReceiveAsync(segment, CancellationToken.None);
                    count += result.Count;
                }


                var message = Encoding.UTF8.GetString(buffer, 0, count);
                Console.WriteLine(">" + message);
            }
        }

 

 

For using Basic Authentication, make sure to set ws.Options.SetRequestHeader properly as shown on this example.

 

Finally, add those lines on the Program.cs in order to start running:

 

            var clientTask1 = ChannelsTest.Client();
            Console.WriteLine("Press enter to finish");
            Console.ReadLine();

 

 

You are supposed to get messages every 30 seconds like this:

 

 

Conclusion

 

I hope you this is a great resource for you to start writing C# applications using PI Web API Batch and Channels. If you have comments or questions, please don't hesitate to post a comment here.

 

Have fun!

Introduction

 

There has been some questions about how to create an ASP.NET Web Application that monitors long running tasks for retrieving a large amount of data from the PI System here on PI Square. There are many different solutions for this topic. On this blog post, I will show you how to achieve this goal using ASP.NET Signal R. As I have already written about how to use PI AF SDK with ASP.NET Signal R, I will focus on the web app itself.

 

ASP.NET Signal R was the selected technology to monitor long running tasks because it enables the server to start a communication with the clients. By using AJAX HTTP, requests to the server get a response but there is no way for a server to send autonomous messages for its clients. Therefore, when using ASP.NET Signal R there is no need for the client to keep making HTTP requests frequently in order to get the current status of the background task.

 

Creating the project on Visual Studio

 

Before we start, the web application that will be developed on this blog post is available on GitHub. Click here to visit the GitHub repository and download the source code package if you want.

 

Let's start by creating an ASP.NET Web Application project in Visual Studio 2013 or 2015.

 

 

Please select the Empty template, but do not forget to add folders and core references for MVC and Web API.

 

 

 

Using NuGet Package Manager, install jQuery, Bootstrap and ASP.NET Signal R.

 

Create a HomeController.cs file under the Controller folder and Index.cshtml under Views\Home folder with the following content:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;


namespace AFSDKProgressBarSignalR.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    }
}

HomeController.cs

 


@{
    Layout = null;
}


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PI AF SDK Progress Bar Application with Signal R</title>
    <link href="~/Content/bootstrap.css" rel="stylesheet" />
    <link href="~/Content/Site.css" rel="stylesheet" />
</head>
<body>
    <div class="container body-content">
        <div class="progress">
            <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
                0%
            </div>
        </div>
        <div class="jumbotron">
            <h1>AF SDK Progress Bar Demo</h1>
            <p id="processing-tag"></p>
            <p class="lead">Click on the button below to transfer all values from the PI Data Archive! </p>
            <p><button id="btn-retrieve-data" class="btn btn-primary btn-lg">Retrieve PI Data!</button></p>
        </div>
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - OSIsoft</p>
        </footer>
    </div>
    <script src="~/Scripts/jquery-2.2.3.js"></script>
    <script src="~/Scripts/jquery.signalR-2.2.0.js"></script>
    <script src="~/Scripts/bootstrap.js"></script>
    <script src="/signalr/hubs" type="text/javascript"></script>
    <script src="~/Scripts/app.js"></script>
</body>
</html>



Index.cshtml

 

The HomeController is the only Controller needed to display the only page of the web site.

 

On the Index web page, we have added the progress bar provided by Bootstrap and a button to start this process. Note that several CSS and JavaScript libraries such as jQuery, Twitter Bootstrap 3 and Signal R needed to be referenced on this page.

 

When the user clicks on the button, the progress bar should move from 0% and reach 100% when the process has finished its execution. But which background task are we talking about?

 

The background task

 

For the purpose of this blog post, we will take all PI Points from a PI Data Archive and try to get 150 values from each PI Point if possible. Create a new class PISystemWrapper on the project root and add a method called RetrieveData() which is its main method. Finally, we will add some properties that are going to be available for thee main thread to have enough information to monitor the process. The three public properties are:

 

  • CurrentPIPointNumber --> number of PI Points whose data was already retrieved
  • CurrentPIPointName --> the name of the current PI Point being processed within the for loop
  • TotalPIPoints --> The number of all PI Points of the PI Data Archive

 

The complete code snippet of PISystemWrapper.cs is:

 

using OSIsoft.AF.Asset;
using OSIsoft.AF.PI;
using OSIsoft.AF.Time;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Web;


namespace AFSDKProgressBarSignalR.
{
    public class PISystemWrapper
    {
        public PISystemWrapper()
        {
            CurrentPIPointNumber = 0;
        }
        public int CurrentPIPointNumber { get; private set; }
        public string CurrentPIPointName { get; private set; }
        public int TotalPIPoints { get; private set; }
        public int RetrieveData()
        {
            PIServer piServer = new PIServers()["MARC-PI2014"];
            IEnumerable<PIPoint> piPoints = PIPoint.FindPIPoints(piServer, "*", true);
            TotalPIPoints = piPoints.Count();
            CurrentPIPointNumber = 0;
            int totalValues = 0;
            foreach (PIPoint piPoint in piPoints)
            {
                CurrentPIPointNumber++;
                CurrentPIPointName = piPoint.Name;
                Debug.WriteLine(piPoint.Name);
                AFValues values = piPoint.RecordedValuesByCount(new AFTime("*-1500d"), 150, true, OSIsoft.AF.Data.AFBoundaryType.Inside, string.Empty, true);
                totalValues = totalValues + values.Count;
            }
            return totalValues;
        }
    }
}

 

Note that there are more efficient ways to obtain 150 values from each PI Point which require more advanced techniques such as bulk calls. Nevertheless, the simplest implementation was selected as the purpose of this blog post is to teach you how to monitor a background task.

 

The PIHub

 

The next step is to create a hub called PIHub with a method called GetData() which can be called by any web client. PIHub derives from the Hub class from ASP.NET Signal R.

 

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;


namespace AFSDKProgressBarSignalR.Hubs
{
    [HubName("piHub")]
    public class PIHub : Hub
    {
        public void GetData()
        {
            PISystemWrapper piSystemWrapper = new PISystemWrapper();
            Task<int> retrieveDataTask = Task.Factory.StartNew<int>(piSystemWrapper.RetrieveData);
            int lastCurrentPIPointNumber = 0;
            while (retrieveDataTask.IsCompleted == false)
            {
                if (piSystemWrapper.CurrentPIPointNumber - lastCurrentPIPointNumber > 100)
                {
                    lastCurrentPIPointNumber = lastCurrentPIPointNumber + 100;
                    Clients.Caller.updateProgressBar(piSystemWrapper.CurrentPIPointName, piSystemWrapper.CurrentPIPointNumber, piSystemWrapper.TotalPIPoints);
                }
                System.Threading.Thread.Sleep(1000);
            }
            retrieveDataTask.Wait();
            Clients.Caller.alertFinalValue(retrieveDataTask.Result);
        }
    }
}

 

A new task will start to run the RetrieveData() method from piSystemWrapper class and the main thread will check every second the state of the background task. It might send the results only to the caller through the C# Clients.Caller.updateProgressBar and  Clients.Caller.alertFinalValue methods which will call the JavaScript $.connection.piHub.updateProgressBar and  $.connection.piHub.alertFinalValue functions of the caller. Those functions are written on the app.js, which is also referenced on the Index.cshtml. The updateProgressBar function manipulate the DOM using jQuery in order to change the value of the progress bar and to show the current PI Point name being processed by the background thread. The alertFinalValue function just display an alert. The content of app.js is shown below.

 

 

var progressBar = $('.progress-bar');
$(function () {
    var broadcaster = $.connection.piHub;


    broadcaster.client.updateProgressBar = function (piPointName, count, totalCount) {
        $("#processing-tag")[0].innerText = 'Processing:' + piPointName;
        value = Math.round(count * 100 / totalCount);
        message = count + '/' + totalCount;
        progressBar[0].innerText = message;
        progressBar.css('width', value + '%').attr('aria-valuenow', value);
    }


    broadcaster.client.alertFinalValue = function (totalValues) {
        alert(totalValues + " events retrieved from the PI System");
    }


    $.connection.hub.start().done(function () {
        $('#btn-retrieve-data')[0].onclick = function () {
            broadcaster.server.getData();
        }
    });
});

app.js

 

 

When the user clicks on the only button of the page, the broadcaster.server.getData() JavaScript function is called which then calls the C# GetData() method of PIHub class. Finally, we need to load the ASP.NET Signal R libraries on the pipeline through the app.MapHubs() method defined on the Startup class that should be located on the project root.

 

 

using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Owin;


[assembly: OwinStartupAttribute(typeof(AFSDKProgressBarSignalR.Startup))]
namespace AFSDKProgressBarSignalR
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            HubConfiguration configuration = new HubConfiguration();
            app.MapHubs(configuration); 
        }
    }
}

 

 

Time to test!

 

Below is a screenshot of the web app we have developed on this blog post. We can see that on the moment that this screenshot was taken, 19474 out of 39012 PI Points were already processed!

 

 

 

 

Conclusion

 

Hopefully, you will find it easier to develop web pages that need to show background tasks information running on the web server by referring to this blog post. I know there are other ways to implement similar behavior but this one should be considered before drawing conclusions about which one you should use.

 

Finally, if you have any questions, just post a comment!

CurrentPIPointNum

ber

We are excited to present the TechCon Programming Hackathon 2016 winners!

 

The theme of this year's Hackathon was Smart Cities. The San Diego International airport kindly provided a sample of their data for our event, including more than 16,000 PI tags. Participants were encouraged to create killer applications for the airport by leveraging the PI System infrastructure.

 

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

  • PI Server 2016 Beta
  • PI Web API 2016 Beta
  • PI Coresight 2016  Beta
  • PI OLEDB Enterprise 2016  Beta

 

The elements of the provided AF database fall into 4 main categories:

  • EV (electric vehicle) chargers
  • HVAC (heating, ventilation, and air conditioning)
  • Utilities usage (electricity meters)
  • Flight traffic (arrival/departure info)

 

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 24 hours, eight groups were able to finish their app and present to the judges!

 

Prizes:

1st place: GoPro Hero 4, one year free subscription to PI Developers Club, one time free registration at OSIsoft Users Conference over the next 1 year

2nd place: Bose Wireless Headphone, one year free subscription to PI Developers Club, 50% discount for registration at OSIsoft Users Conference over the next 1 year

3rd place: Amazon Echo, one year free subscription to PI Developers Club

 

 

Congratulations to everyone who participated!

 

Without further ado, here are the winners!

 

1st place - OhmMan

 

The team members were: Jon Horton, James Hughes, David Rose, Yuankang (Matt) Chen.

 

 

 

hackathon-winners.jpg

 

 

P4060079.JPG

 

 

 

Team OhmMan developed a web application that shows Key Point Indicators for energy usage, environmental impact and displays charging UV usage data. One of the web pages for the EV Chargers embedded a PI Coresight 2016 display. As PI Coresight 2016 is fully developed in HTML5, it works pretty well on mobile browsers.

 

 

The app also enables customers to view the free EV Charger locations on a monitor mounted at the entrance to the parking lot.

 

 

Finally, another display of the app shows key environmental indicators in real time.

 

 

The team used the following technologies:

  • PI Coresight with AF Calculated attributes
  • PI ProcessBook
  • ASP.NET MVC
  • PI AF SDK
  • jQuery

 

2nd place - Cool Energy

 

The team members were: Rick Rys, Thiago Bacic, Stefano D'Avila Bassan and Brandon Lake!

 

P4060067.JPG

 

Team Cool Energy created a web app that shows a KPI Dashboard providing a quick visual understanding of potential energy cost savings. This app enables the airport to find areas where they are being overheated, for instance. The graphics provided by the app indicate the regulation of comfort zone in each terminal area with dynamic Psychometric charts.

 

This app also helps:

  • Compute equipment efficiency and alert problems (Boilers, Chillers, Zones, piping)
  • Determine controller performance  (to identify problem equipment and zones)

 

Here are some screenshots presented by Cool Energy!

 

 

 

3rd place - FootGen

 

The team members were: Robert Raesemann, Ionut Buse, Rhys Kirk, Gael Cottet.

 

P4060069.JPG

 

The vision of the FootGen team is to generate foot energy within the San Diego International airport and they created a application to monitor harvest from passengers. They developed a web app using PI ProcessBook displays embedded on PI Coresight to monitor the main system and also a customer portal using PI AF SDK and Angular JS. Some work on the PI System was required for the application to work:

 

  • Enriched the flight data on the AF Database (passengers, terminal …)
  • Used AF Analytics to calculate FootGen generation from passenger numbers per flight
  • Rolled up to Terminal Level

 

The team used the following technologies:

  • ASP.NET
  • AngularJS
  • Web Service + AF SDK
  • Event Frames
  • AF Analytics
  • PI Coresight

 

Here are some screenshots presented by FootGen!

 

 

 

 

 

Hello Everyone,

 

Today I am presenting you a technical solution to query PI Web API data from Excel.  I was thinking of this for a while now and I think this may be very valuable for our community because:

  • I know you love Excel , and I am also quite certain we will still see VBA around for a while, until everything becomes Javascript maybe? (you should start to learn it if you have not started yet!).
  • It brings access to AF from VBA via PI Web API, something that up to now could only be done using a COM Wrapper (for Processbook VBA or Excel VBA).
  • It also brings a solution to avoid using the PI SDK, even though this is still a good piece of technology, we have to prepare and detach ourselves a little bit from it...

 

What to expect?

At this time, I am just showing you the proof of concept, it covers having everything setup to make API calls and get the results.  It is to be expected that this will require some work to parse the results returned by the API.

I'd like to hear from you if you would like it to be a community project, a library that we could build together, I am up to it.

 

Library used

I am using an Excellent library called VBA-Web.  It is well documented and made to work with complex web services.

GitHub Repository and Download    |     Documentation

 

Authentication

The library seems quite mature and has many authenticators available.

I have used the Windows Authenticator in my environment and it worked well.  I am not yet certain though that this Windows Authenticator will work in all situations as it seems to be limited to NTLM.

Please let me know if you find that this does not work for you I will update the content in this section.

 

Depending on your installation, PI Web API can be configured for :

  • Basic Authentication
  • Windows Authentication (Kerberos)
  • Anonymous Authentication (basically, no security.)

 

So this mean you may need to select a different Authenticator, they are available here.

 

Step by Step Approach

Setting up the Excel File

  • First, you'll need to download the VBA-Web .zip file, available on GitHub.
  • Unzip the content, and copy the file "VBA-Web - Blank.xlsm" to a working directory.

  • Rename the file to something more meaningful to your intention, e.g. PIAPIWeb, AFStats, etc.
  • Open the file
  • Copy the content of the Authenticator from GitHub, here. (The rest of these steps are assuming the usage of the Windows Authenticator).  In GitHub, Click on the Raw view to copy the content easily.
  • Create a new text file on your computer, called WindowsAuthenticator.cls
  • Paste the content into the file and save the file.
  • Open the VBA Editor in Excel (Alt+F11) and navigate to Class Modules.
  • Right Click on "Class Modules" then select import file.  Then Select the file WindowsAuthenticator.cls you just created

 

Creating the module to get data from the PI Web API

Ok, so now we are ready to write some code for our own purpose: PI Web API.

  • Create a new Module in the Project, I called it ModPIWebAPI, you can give it the name you like.
  • Then copy paste this code into the Module  and change the PI Web API Address on line 2:
' Base URL of the PI Web API Installation - you should configure it to your server
Const BASE_URL As String = "https://megatron/piwebapi/"

Private Client As WebClient

' initializes the Web Client
Private Sub Init()
        
    ' set authentication to windows authentication
    ' Check the documentation if you need to set a different authentication type.
    Set Authenticator = New WindowsAuthenticator
    
    Set Client = New WebClient
    Client.baseUrl = BASE_URL
    Set Client.Authenticator = Authenticator
    
    ' use only for trusted servers, you should need this only for self signed certificates or with your development server
    ' Client.Insecure = True

End Sub

' This is a "generic function to get content from the PI Web API
' it returns a Web Response object
' see details here: http://vba-tools.github.io/VBA-Web/docs/#/WebResponse
'e.g. makeRequest("assetservers")
'     makeRequest("system/userinfo")
Public Function MakeRequest(resource As String) As WebResponse

    Call Init
    Dim Request As New WebRequest
    Request.Method = WebMethod.HttpGet
    Request.resource = resource

    Dim Response As WebResponse
    
    ' You should comment the line below after you are done with your development
    ' this is quite useful
    WebHelpers.EnableLogging = True
    Set Response = Client.Execute(Request)
    
    ' returns the results
    Set MakeRequest = Response


End Function

' This method returns the WebID of an AF Server
'  e.g.
' GetAssetServerWebId("megatron") --> S0glSFglj -OUWvbNrrcijzwgTUVHQVRST04
Public Function GetAssetServerWebId(ByVal name As String) As String


    Dim Response As WebResponse
    Set Response = MakeRequest("assetservers")
    For Each Item In Response.Data("Items")
        If UCase(Item("Name")) = UCase(name) Then
            GetAssetServerWebId = Item("WebId")
        End If
    Next Item
End Function

 

  • You can call the MakeRequest to Retrieve content from the PI Web API, any call.e.g:
makeRequest("assetservers")
makeRequest("system/userinfo")
  • GetGetAssetServerWebId implement some logic based on the MakeRequest method and it also shows how to parse the Response.Data property.  It is probably the beggining of more complex calls that you need to make with the PI Web API.  You can use this a a beggining for your own implementation

Client certificate and the insecure option

   Client.Insecure = True

You can use this option if you work in a development environment and you don't have proper certificates.  You need this option set to true when you see a screen like below in your browser when navigating to the PI Web API URL:

It is present in a commend in the Init() function of the above code.

Debugging

The WebClient provided in the library has a very nice debugging feature.

It is enabled on line 36 of the code above like this:

WebHelpers.EnableLogging = True

 

Here is an example of the log output after the client made a request - (in the immediate window of the VBA Editor (Ctrl+G))

--> Request - 5:14:29 PM
GET https://megatron/piwebapi/system/userinfo
User-Agent: VBA-Web v4.0.21 (https://github.com/VBA-tools/VBA-Web)
Content-Type: application/json
Accept: application/json
Content-Length: 0


<-- Response - 5:14:29 PM
200 OK
Date: Fri, 08 Apr 2016 15:14:29 GMT
Content-Length: 165
Content-Type: application/json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
WWW-Authenticate: oYGyMIGvoAMKAQChCwYJKoZIgvcSAQICooGaBIGXYIGUBgkqhkiG9xIBAgICAG+BhDCBgaADAgEFoQMCAQ+idTBzoAMCAReibARqRpisjsb4iwxaSPCsGU9VPnE48T7rSyN3Oyx0HWhqLAnDkITIAG085Jmmmg+2cEPhiCTNFtiy+gCUzl9LVUa3N0ZJDyf5kBHm0PZ6L6NFcFdTryz1NGXKhF9zGGPTR0vIIGA9A84FZRo80A==


{"IdentityType":"WindowsIdentity","Name":"DECEPTICONS\\megatron","IsAuthenticated":true,"SID":"S-1-5-21-1889902799-2021996611-654431455-1104","ImpersonationLevel":4}

 

More Features

Make sure that you have a look at the library documentation, is has many Web Helpers, such as UrlEncode/Decode that can be useful to prepare search queries and many many more.

 

Conclusion

As you can see, this implementation is very basic at the moment and you will need to work on parsing the results returned from the WebResponse object. In my next post I'll try to figure out how to make it more straightforward.

I also hope that you will like this library as much as me and I also believe that this opens a door to something new!

I am looking forward for your comments to see what you imagine can be done with it.

 

I attached my work on this post. Very basic as I said

--

I would like also to give a special thanks to Tim Hall, his VBA-tools library is just amazing!

His work his licensed under MIT License, this one of the most permissive license, you can do pretty much anything with it.

 

Edit 2016-05-27:

Did you ever try calling PI Web API from VBA within ProcessBook itself?

Raymond Verhoeff 

I just tested with PI ProcessBook and it also works, added the file to this post. Added details about the Insecure option as well that help when certificate is not secure, see Client certificate and the insecure option section above.

In the PI ProcessBook file, I implemented a small logic to retrieve the user info from the PI Web API.  To make it easier to make a quick test.

 

It is that time of the year again! Before jumping into this year results I like to open with a short but very important point. We recognize that our community has matured quite a bit. As a result and to leverage our bigger and more diverse community we are considering adding new categories to our All-Star program next year. It is to recognize new comers as well as experts in certain technologies. If you have any ideas to share with us along these lines please share your thoughts.

 

We are very pleased to announce our 2016 edition of the PI Developers Club All-Stars! These individuals brought tremendous amount of value to the PI Geeks community by constant presence in discussion, creating content, and helping others. We would not be where we are without great contributions from these individuals.

 

PI Developers Club Community All-Stars 2016

  1. Asle Frantzen, Amitec AS
  2. Rhys Kirk, TGG
  3. Roger Palmen, CGI

 

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 2016 or UC San Francisco 2017 - not including labs)
  4. Q Grant Fossil Smart Watch

 

PI Developers Club OSIsoft All-Stars 2016

  1. Dan Fishman, OSIsoft
  2. Stephen Kwan, OSIsoft
  3. Mike Zboray, OSIsoft

 

They win:

 

  1. Recognition in the community
  2. Q Grant Fossil Smart Watch

 

Please join me in congratulating these All-Stars for 2016! Are you a 2017 All-Star?

I want to share Delete PI Data Archive tag values tool.

Some customers have requested to delete events for specific time span and specific values.

I've created sample tool by AFSDK.

Code is in GitHub.

GitHub - kenji0711/DeleteTagEvents: DeleteTagEvents Created by Visual Studio 2013 / C# / AFSDK

This tool can delete tag's events.

You can specify followings.

PI Tags name

All Events/ Specific Events (Manual entery of value)

Start Time/End Time

If you have interesting, please check this tool.

Also remember that deleting PI tag values effects performance for PI Data Archive.

So I recommend to delete a few events/tags.

Filter Blog

By date: By tag: