Skip navigation
All Places > PI Developers Club > Blog > 2015 > July
2015

Introduction

 

Yesterday, I have published a blog post about a sample application developed using HTML5 and Javascript/jQuery, showing the new features of PI Web API 2015 R2. One of the new enhancements is bulk calls (read and write) for multiple streams (PI Points and Attribute) through only one unique HTTP request. Nevertheless, a prerequisite for using this cool feature is to know the WebId from each stream in advance.

 

On the sample application, we were able to overcome this issue using the $.when() method from jQuery:

 

   var ajaxPt1 = getAttributeWebId(attributePaths[0], null, errorCallBack);
        var ajaxPt2 = getAttributeWebId(attributePaths[1], null, errorCallBack);
        var ajaxPt3 = getAttributeWebId(attributePaths[2], null, errorCallBack);
        var data = [];
        $.when(ajaxPt1, ajaxPt2, ajaxPt3).done(function (r1, r2, r3) {
            var results = new Array(3);
            results[0] = r1[0];
            results[1] = r2[0];
            results[2] = r3[0];
               ......
          });

 

 

The variables ajaxPt1, ajaxPt3, ajaxPt3 are ajax functions that will get the webId from the PI Points of the array. The method when() will execute them and will continue with the done() method as soon as all the requests have their execution finished. The results of those ajax requests are available on the r1, r2 and r3 variables which let you get the webIds and proceed with the bulk calls.

 

The natural question for a .NET developer is: how to do that in C#? The sample application from this blog post answers this question. Click here to download the source code package.

 

Calling RESTful web services in .NET

 

Let's remember how to call actions from PI Web API in C#. The code snippet below was extracted from our blog post:

 

internal static dynamic MakeRequest(string url)
        {
            WebRequest request = WebRequest.Create(url);
            request.Credentials = new NetworkCredential("marc.adm", "xxxxxx");  
            request.ContentType = "application/json";
            WebResponse response = request.GetResponse();
            using (StreamReader sw = new StreamReader(response.GetResponseStream()))
            {
                using (JsonTextReader reader = new JsonTextReader(sw))
                {
                    return JObject.ReadFrom(reader);
                }
            }
        }

 

I am still using basic authentication on my environment. Setting up Credentials property from the WebRequest will add the Basic Authentication header of the HTTP request. In order to convert the response into a dynamic object, I am using Newtonsoft.Json which can be downloaded using NuGet.

 

For more information, please refer to this blog post.

 

Creating an array of tasks

 

First, we define a List<string> with all the PI Point names and a dynamic object which will store the final response containing all the compressed values, Then, we create an array of tasks responsible for getting the webIds from the streams. Each task will get the webId from each stream. The code snippet is below:

 

            
            List<string> piPointNames = new List<string>() { "sinusoid", "sinusoidu", "cdt160" };


            //this variable will store the final response with the compressed values from all the PI Points from the array
            dynamic finalResponse = null;


            // create an array of tasks
            Task<string>[] tasks = new Task<string>[piPointNames.Count];


            for (int i = 0; i < piPointNames.Count; i++)
            {


                // create a new task
                tasks[i] = new Task<string>((piPointName) =>
                    {


                        //get the webId from the selected PI Point
                        string url = @"https://marc-web-sql.marc.net/piwebapi/points?path=\\marc-pi2014\" + piPointName;
                        dynamic response = MakeRequest(url);
                        string webId = response.WebId.Value.ToString();


                        //the webIds will be available by accessing the property Task.Result
                        return webId;
                    }, piPointNames[i]);
            }

 

Note that although we have created and defined the tasks, they have not started yet.

 

Set up multitask continuation

 

When all tasks have finished storing the WebIds from the streams, the url for getting the compressed values needs to be generated. We create another task called continuation which will be executed when all of the antecedents task have their execution finished. The continuation task will access the Result property from each antecedent Task for geting the webIds. Adding the start time and end time, all the inputs are available to complete this task by calling the MakeRequest(url) method again. Finally, the antecedent tasks are started and the main thread waits for the continuation task to complete. The final code snippet is below:

 

            Task continuation = Task.Factory.ContinueWhenAll(tasks, antecedents =>
            {
                //in order to generate an url with all webids to get compressed values from all PI Points within the array, the antecents tasks need be accessed
                string webIdsSection = string.Empty;
                foreach (Task<string> t in antecedents)
                {
                    webIdsSection += "&webid=" + t.Result;
                }
                //then, we can generate the url with the start time and end time
                string url = @"https://marc-web-sql.marc.net/piwebapi/streamsets/recorded?" + webIdsSection.Substring(1) + "&startTime=*-200d&endTime=*";
                finalResponse = MakeRequest(url);
            });


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


            continuation.Wait();


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

 

 

Conclusions

 

Although the PI Web API 2015 R2 provides bulk calls, you need to know the webIds in order to use this feature. If you don't have them available, you must get them prior to calling bulk methods. This blog posts provides a C# example of how to overcome this challenge by creating antecedent tasks for getting the webIds and a continuation task for the bulk call. Using tasks increases the performance of your application as we work on a multi-threaded environment making parallel calls to the PI Web API.

 

I hope you find this blog post useful and I would like to thank you for reading!

Introduction

 

As you know, PI Web API 2015 R2 is already released! That is why I have decided to write a blog post to show its main new features through a sample application using HTML5 and jQuery. You can download the source code package here.

 

Objetives

 

The enhancements of this new version taken from the release notes are:

  • Support for PI Server (Data Archive and AF) 2015
    • Support for reading and writing Future Data
    • Support for creating PI Points with the Future attribute
    • Support for retrieving end-of-stream values from Streams and StreamSets
    • Support for hidden and excluded AF Attributes
    • Support for the PI AF Server 2015 security model
  • Bulk writes of time series data (single and multiple value)
  • Ad-hoc bulk reads and writes of time series data
  • The ability to capture values on PI Event Frames
  • Indexed Search contains improvements to reduce crawl times for highly referenced AF Databases.

 

In order to make this work more scalable, a PI Web API module was developed. Although the structure of this module was taken from my other blog post Using PI Web API on HTML5 with jQuery, its methods are completely different in order to demonstrate properly the new features of the product.

 

This sample application has 7 examples which call methods from the module described. The idea was to make a module that you can easily use in your own HTML5 web application.

 

 

Preparing the environment

 

This sample application was developed using the following settings for PI Web API:

 

  • AuthenticationMethods: only Basic
  • CorsHeaders: *
  • CorsMethods: GET, POST, PATCH, OPTIONS
  • CorsOrigins: http://localhost:54622
  • CorsSupportCredentials: True

 

The domain of the CorsOrigins is localhost with a uncommon port because this is the domain which Visual Studio chooses by default for development when I run my web application.

The options above are not mandatory, but you might need to overcome some issues in case you have a different environment.  If the domain where your application runs is the same from the PI Web API (including the port), then you won’t need to care with CORS attributes.

 

You can check your PI Web API settings using PI System Explorer. Please refer to the PI Web API User Guide for more information.

 

 

PI Web API module

 

The JavaScript module has 8 public methods available:

 

  • SetBaseServiceUrl(baseUrl) --> This method define the BaseServiceUrl so that all the other methods could connect to the correct PI Web API endpoint on later requests This method should be called before any other method otherwise you will receive an error.
  • CreateFuturePIPoint(piPointName, piDataArchiveName, updateDOM) --> Creates a new future PI Point whose name is piPointName on the PI Data Archive whose name is stored on the piDataArchiveName variable.  Finally, updateDOM is a function which is executed in case  the last ajax request run successfully. This function will have the same purpose for all the other methods.
  • UpdateValuesForSingleStream(piPointName, piDataArchiveName, updateDOM) --> sends 100 future random values for a given future PI Point.
  • UpdateValuesForMultipleStreams(piPointNames, piDataArchiveName, updateDOM) --> sends 100 future random values for each future PI Point within the piPointNames array.
  • GetValuesFromSingleStream(piPointName, piDataArchiveName, startTime, endTime, updateDOM) --> gets the compressed values from a particular PI Point on a given time range, including future values.
  • GetValuesFromMultipleStreams(attributePaths, startTime, endTime, updateDOM) --> gets the compressed values for a given time range from all attributes whose path is included within the attributePaths.
  • GetEndOfTheStream(piPointName, piDataArchiveName, updateDOM) --> get the end of the stream from the sinusoid and from the piPointName. End of the stream is a new concept introduced with PI AF SDK 2.7 and the PI Server 2015. Please refer to those product documents for more details.
  • MakeAttributeHidden(attributePath, updateDOM) --> change the value from the isHidden and description property for a given AFAttribute specified by its path.

 

The methods above can connect to the PI Data Archive and also to the PI AF Sever. No matter which one of the two options you choose, the way you do it are quite similar. The complete code of this module is on the file piwebapi_wrapper.js within the Scripts folder of source code package or below:

 

 

var piwebapi = (function () {
    var base_service_url = 'NotDefined';
    //Specify the domain user name
    var username = 'username';


    //Specify the domain user password
    var password = 'password';


    //Generate 100 future random values (one value per day)
    function generateRandomFutureValues(n) {
        var values = [];
        for (i = 0; i < 100; i++) {
            var newValue = new Object();
            newValue.Timestamp = "* + " + i.toString() + "d";
            newValue.Value = Math.random();;
            newValue.Good = true;
            newValue.Questionable = false;
            values.push(newValue);
        }
        return values;
    }


    //Get PI Point Web Id
    function getPIPointWebId(piDataArchiveName, piPointName, successCallBack, errorCallBack) {
        baseUrlCheck();
        var url = base_service_url + 'points?path=\\\\' + piDataArchiveName + '\\' + piPointName;
        return processJsonContent('GET', null, url, successCallBack, errorCallBack);
    }


    //Get PI Data Archive Web Id
    function getPIDataArchiveWebId(piDataArchiveName, successCallBack, errorCallBack) {
        baseUrlCheck();
        var url = base_service_url + "dataservers?name=" + piDataArchiveName;
        return processJsonContent('GET', null, url, successCallBack, errorCallBack);
    }


    //Get AF Attribute Web Id
    function getAttributeWebId(attributePath, successCallBack, errorCallBack) {
        baseUrlCheck();
        var url = base_service_url + "attributes?path=" + attributePath;
        return processJsonContent('GET', null, url, successCallBack, errorCallBack);
    }




    //Returns the ajax function that will be responsbile for communicating with PI Web API. The request starts after the method $.when method is called.
    function processJsonContent(type, data, url, successCallBack, errorCallBack) {
        return $.ajax({
            type: type,
            headers: {
                "Content-Type": "application/json; charset=utf-8",
            },
            url: url,
            cache: false,
            data: data,
            async: true,
            username: username,
            password: password,
            
            crossDomain: true,
            xhrFields: {
                withCredentials: true
            },
            success: successCallBack,
            error: errorCallBack
        });
        //It seems that contentType option doesn't work pretty well. Using the headers option seems to work better.
        //crossDomain and xhrFields with  withCredentials: true needed to be used because the Anonymous authentication is disabled
        //and that my web application and PI Web API are running in different domains.
    }




    function createFuturePIPoint(piPointName, piDataArchiveName, updateDOM) {
        
        baseUrlCheck();
        var errorCallBack = function (error) {
            alert(error.responseJSON.Errors[0]);
        }


        var ajax = getPIDataArchiveWebId(piDataArchiveName, null, errorCallBack);
        $.when(ajax).then(function (dataServerJson, textStatus, jqXHR) {
            //alert(jqXHR.status); // Alerts 200
            var url = base_service_url + 'dataservers/' + dataServerJson.WebId + '/points';
            var data = new Object();
            data.Name = piPointName;
            data.PointClass = "classic";
            data.PointType = "Float32";
            data.Future = true;
            var jsonString = JSON.stringify(data);
            var ajax2 = processJsonContent('POST', jsonString, url, updateDOM, errorCallBack);
            $.when(ajax2).then(function (data, textStatus, jqXHR) {
                alert('Future PI Point was created successfully!');
            });


        });


    }


    function writeFutureValuesForStream(piPointName, piDataArchiveName, updateDOM) {
        baseUrlCheck();
        var errorCallBack = function (error) {
            alert(error.responseJSON.Errors[0]);
        }


        var ajax = getPIPointWebId(piDataArchiveName, piPointName, null, errorCallBack);
        $.when(ajax).then(function (piPointJson, textStatus, jqXHR) {
            //alert(jqXHR.status); // Alerts 200
            var url = base_service_url + 'streams/' + piPointJson.WebId + '/recorded';
            var futureValues = generateRandomFutureValues(100);
            var jsonString = JSON.stringify(futureValues);
            var ajax2 = processJsonContent('POST', jsonString, url, updateDOM, errorCallBack);
            $.when(ajax2).then(function (data, textStatus, jqXHR) {
                alert('Future values were sent successfully!');
            });


        });
    }




    function readFutureValues(piPointName, piDataArchiveName, startTime, endTime, updateDOM) {
        baseUrlCheck();


        var errorCallBack = function (error) {
            alert(error.responseJSON.Errors[0]);
        }


        var ajax = getPIPointWebId(piDataArchiveName, piPointName, null, errorCallBack);
        $.when(ajax).then(function (piPointJson, textStatus, jqXHR) {
            //alert(jqXHR.status); // Alerts 200
            var url = base_service_url + 'streams/' + piPointJson.WebId + '/recorded?startTime=' + startTime + '&endTime=' + endTime;
            var ajax2 = processJsonContent('GET', null, url, updateDOM, errorCallBack);
            $.when(ajax2).then(function (data, textStatus, jqXHR) {


            });
        });
    }






    function getEndOfTheStream(piPointName, piDataArchiveName, updateDOM) {
        baseUrlCheck();
        var errorCallBack = function (error) {
            alert(error.responseJSON.Errors[0]);
        }


        var ajax = getPIPointWebId(piDataArchiveName, piPointName, null, errorCallBack);
        $.when(ajax).then(function (piPointJson, textStatus, jqXHR) {
            //alert(jqXHR.status); // Alerts 200
            var url = base_service_url + 'streams/' + piPointJson.WebId + '/end';
            var ajax2 = processJsonContent('GET', null, url, updateDOM, errorCallBack);
            $.when(ajax2).then(function (data, textStatus, jqXHR) {


            });
        });
    }






    function makeAttributeHidden(attributePath, updateDOM) {
        baseUrlCheck();
        var errorCallBack = function (error) {
            alert(error.responseJSON.Errors[0]);
        }


        var ajax = getAttributeWebId(attributePath, null, errorCallBack);
        $.when(ajax).then(function (attributeJson, textStatus, jqXHR) {
            //alert(jqXHR.status); // Alerts 200
            var url = base_service_url + 'attributes/' + attributeJson.WebId;
            var data = new Object();
            data.isHidden = true;
            data.Description = "Test description 124";
            var jsonString = JSON.stringify(data);
            var ajax2 = processJsonContent('PATCH', jsonString, url, updateDOM, errorCallBack);


            $.when(ajax2).then(function (data, textStatus, jqXHR) {
                alert('Hidden and description properties were changed successfully!');
            });
        });
    }


    function writeFutureValuesForStreams(piPointNames, piDataArchiveName, updateDOM) {
        baseUrlCheck();
        var errorCallBack = function (error) {
            alert(error.responseJSON.Errors[0]);
        }


        var ajaxPt1 = getPIPointWebId(piDataArchiveName, piPointNames[0], null, errorCallBack);
        var ajaxPt2 = getPIPointWebId(piDataArchiveName, piPointNames[1], null, errorCallBack);
        var ajaxPt3 = getPIPointWebId(piDataArchiveName, piPointNames[2], null, errorCallBack);
        var data = [];
        $.when(ajaxPt1, ajaxPt2, ajaxPt3).done(function (r1, r2, r3) {
            var results = new Array(3);
            results[0] = r1[0];
            results[1] = r2[0];
            results[2] = r3[0];


            for (var i = 0; i < results.length; i++) {
                var obj = new Object();
                obj.WebId = results[i].WebId;
                obj.Items = generateRandomFutureValues(100);
                data.push(obj);
            }


            var jsonString = JSON.stringify(data);
            var url = base_service_url + 'streamsets/recorded';
            var ajax = processJsonContent('POST', jsonString, url, updateDOM, errorCallBack);


            $.when(ajax).done(function (a1) {
                alert('Future values were sent successfully to the 3 pi tags!');
            });
        });
    }




    function readValuesFromMultipleStreams(attributePaths, startTime, endTime, updateDOM) {
        baseUrlCheck();
        var errorCallBack = function (error) {
            alert(error.responseJSON.Errors[0]);
        }


        var ajaxPt1 = getAttributeWebId(attributePaths[0], null, errorCallBack);
        var ajaxPt2 = getAttributeWebId(attributePaths[1], null, errorCallBack);
        var ajaxPt3 = getAttributeWebId(attributePaths[2], null, errorCallBack);
        var data = [];
        $.when(ajaxPt1, ajaxPt2, ajaxPt3).done(function (r1, r2, r3) {
            var results = new Array(3);
            results[0] = r1[0];
            results[1] = r2[0];
            results[2] = r3[0];
            var webIdSection = '';
            for (var i = 0; i < results.length; i++) {
                webIdSection = webIdSection + '&webid=' + results[i].WebId;
            }
            webIdSection = webIdSection.substr(1);
            var url = base_service_url + 'streamsets/recorded?' + webIdSection + '&startTime=' + startTime + '&endTime=' + endTime;
            var ajax = processJsonContent('GET', null, url, updateDOM, errorCallBack);


            $.when(ajax).done(function (a1) {
                alert('Values received successfully from the 3 different attributes!');
            });
        });
    }


    function baseUrlCheck() {
        if (base_service_url == "NotDefined") {
            alert("Service base url was not defined");
        }
    }




    return {
        CreateFuturePIPoint: function (piPointName, piDataArchiveName, updateDOM) {
            createFuturePIPoint(piPointName, piDataArchiveName, updateDOM)
        },
        UpdateValuesForSingleStream: function (piPointName, piDataArchiveName, updateDOM) {
            writeFutureValuesForStream(piPointName, piDataArchiveName, updateDOM);
        },
        UpdateValuesForMultipleStreams: function (piPointNames, piDataArchiveName, updateDOM) {
            writeFutureValuesForStreams(piPointNames, piDataArchiveName, updateDOM);
        },
        GetValuesFromMultipleStreams: function (attributePaths, startTime, endTime, updateDOM) {
            readValuesFromMultipleStreams(attributePaths, startTime, endTime, updateDOM);
        },
        GetValuesFromSingleStream: function (piPointName, piDataArchiveName, startTime, endTime, updateDOM) {
            readFutureValues(piPointName, piDataArchiveName, startTime, endTime, updateDOM);
        },
        GetEndOfTheStream: function (piPointName, piDataArchiveName, updateDOM) {
            getEndOfTheStream(piPointName, piDataArchiveName, updateDOM);
        },
        MakeAttributeHidden: function (attributePath, updateDOM) {
            makeAttributeHidden(attributePath, updateDOM);
        },
        SetBaseServiceUrl: function (baseUrl) {
            base_service_url = baseUrl;
            if (base_service_url.slice(-1) != "/") {
                base_service_url = base_service_url + "/";
            }
        }
    }
}());

 

Web Sample Application

 

This web sample application has two NuGet JavaScript libraries:

 

jQuery 2.1.4 -->  This JavaScript library makes life easier when making HTTP request through Ajax.

Twitter Bootstrap 3.3.5 --> This JavaScript library enhances the design of the web application. It also makes the application responsive, which is a very important feature as web sites are being more accessed by smartphones. I have taken some code snippets from the default ASP.NET MVC template to have a good design in a short amount of time.

 

Before you start testing this sample application, you need to open the app.js file under the Scripts folder and change the first lines according to the following guidelines:

 

serviceBaseUrl --> Defines the PI Web API endpoint.

afServerName and piServerName --> Defines the PI AF Server and PI Data Archves names that PI Web API will connect to.

attributePathForHiddenTest --> Path of the attribute which will have its isHidden and description properties changed.

attributePaths --> array of AF Attribute paths used by the GetValuesFromMultipleStreams() method.

piPointNames --> array of future PI Point names that are going to be created on the first example.

 

Once you start the application, make sure to run Exercise 1 first. It will create 3 PI Points which are going to be used by some of the other exercises as well.

Understanding the method readValuesFromMultipleStreams

 

 

I will explain what happens with the readValuesFromMultipleStreams under the hood. If you are able to understand it, the same logic applies for other methods as well. Here is what happens under the hood:

 

  • baseUrlCheck() --> Checks to see if the serviceBaseUrl, which is the PI Web API endpoint, was defined.
  • errorCallBack --> Defines the function which will be called in case there is an error with any ajax request to PI Web API.
  • getAttributeWebId(attributePaths[0], null, errorCallBack); --> This method returns the $.ajax function which is responsable for the communication to PI Web API. The function is returned but it only runs the HTTP request on the next step.
  • $.when(ajaxPt1, ajaxPt2, ajaxPt3).done(r1,r2,r3) --> This method will start the 3 HTTP requests. When all of three requests have their execution finished, their respective responses will be available on r1, r2 and r3, which contains the webIds for each attribute that are going to be used to generate the new URL for the next ajax request.
  • var ajax = processJsonContent('GET', null, url, updateDOM, errorCallBack); --> After the new URL is generated using the 3 webIds, startTime and endTime, the processJsonContent will return an ajax function ready to get the requested compressed values.
  • $.when(ajax).done(a1) --> Run the new ajax request and it provides the result on the variable a1.

 

 function readValuesFromMultipleStreams(attributePaths, startTime, endTime, updateDOM) {
        baseUrlCheck();
        var errorCallBack = function (error) {
            alert(error.responseJSON.Errors[0]);
        }


        var ajaxPt1 = getAttributeWebId(attributePaths[0], null, errorCallBack);
        var ajaxPt2 = getAttributeWebId(attributePaths[1], null, errorCallBack);
        var ajaxPt3 = getAttributeWebId(attributePaths[2], null, errorCallBack);
        var data = [];
        $.when(ajaxPt1, ajaxPt2, ajaxPt3).done(function (r1, r2, r3) {
            var results = new Array(3);
            results[0] = r1[0];
            results[1] = r2[0];
            results[2] = r3[0];
            var webIdSection = '';
            for (var i = 0; i < results.length; i++) {
                webIdSection = webIdSection + '&webid=' + results[i].WebId;
            }
            webIdSection = webIdSection.substr(1);
            var url = base_service_url + 'streamsets/recorded?' + webIdSection + '&startTime=' + startTime + '&endTime=' + endTime;
            var ajax = processJsonContent('GET', null, url, updateDOM, errorCallBack);


            $.when(ajax).done(function (a1) {
                alert('Values received successfully from the 3 different attributes!');
            });
        });
    }

 

Concerning the logic of the PI Web API module, for most of the methods they can be divided in two parts. The first one gets the WebId or WebIds from attributes, PI Points or PI Data Archive.The second part completes the process using the webId provided by the previous ajax request results.

 

Conclusions

 

PI Web API 2015 R2 supports future data and also introduces bulk calls support for writing and reading data not only for a single stream but also for multiple streams in a single HTTP request. We expected an increase of the performance from your applications in case you take advantages of the bulk actions available on this release properly.

 

 

Stay tuned for the upcoming blog posts!!

The new PowerShell Tools for the PI System have been released! These PowerShell cmdlets are installed with the PI System Management Tools 2015 (3.5.1.7). You might recall that we had a CTP release (version 1.1.0.0) a few years ago, available only to vCampus members. The current version is a rewrite of the previous release, and offer many functional and performance enhancements over the CTP version. It is also available to all licensed customers and PI DevClub members.

 

These PowerShell Tools are designed to provide PI System Administrators the ability to create reusable scripts for common or bulk system management operations. While it is not primarily a data access tool, I will be testing out some data read and write operations to get familiar with the new tools. I am by no means a PowerShell expert: the purpose of the blog post is to bring awareness to the tools available and offer a quick start guide to anyone who is interested!

 

Getting Started

Before we start, please note that PowerShell Tools for the PI System requires Windows PowerShell 4.0 (see release notes). You can check your PowerShell version by running

$PSVersionTable.PSVersion

 

After an installation or upgrade to PI SMT 2015, the PowerShell tools are available as a PowerShell module named OSIsoft.PowerShell. Beginning in Windows PowerShell 3.0, modules are automatically imported when you use a command in the module. Since it is no longer provided as a PSSnapin as in the CTP version, there is no need to calll Add-PSSnapin. This version of the PowerShell Tools is installed in %pihome%\OSIsoft.PowerShell. The path should already been added to the PSModulePath environment variable:

$env:PSModulePath

 

You should see your %pihome% directory listed.

 

To confirm that the OSIsoft.PowerShell module is imported in the current session, run

Get-Module

and look for OSIsoft.PowerShell under the name column. However, the OSIsoft.PowerShell module will only show up if a command in the module has previously been used in the session. If you don't see OSIsoft.PowerShell module in the list, run a command (e.g. Get-Command -Module OSIsoft.PowerShell) and then run Get-Module to check again.

 

To get a list of all the cmdlets available in the OSIsoft.PowerShell module:

Get-Command -Module OSIsoft.PowerShell

 

From the resulting list, you might have noticed that there are no cmdlets available to access PI AF (which were previously included in the vCampus CTP version). The AF cmdlets are actively being developed now and are planned for inclusion in the next release. For more information, please refer to KB01248.

Note: AF Server support to PowerShell Tools for the PI System has been added in the SMT 2015 R2 release!

 

To get the description and example for specific cmdlets, run the Get-Help cmdlet. E.g.

Get-Help Connect-PIDataArchive

 

You can specify specific information to obtain in Get-Help by supplying different parameters (e.g. -examples, -full, etc.).

 

Connecting to the PI Data Archive

First, let's create a connection to the PI Data Archive. Let's check out the Connect-PIDataArchive cmdlet:

Get-Help Connect-PIDataArchive

 

DESCRIPTION

    The Connect-PIDataArchive cmdlet allows a connection to be made to the specified PI Data Archive.  A PI Data Archive can be specified by machine name, or entry in the local Known Servers Table.

 

It looks like we can make a connection to the PI Data Archive by name! (You might recall the CTP version requires us to get a PI Server object first.) To see specific examples:

Get-Help Connect-PIDataArchive -Examples

 

Now, let's attempt a connection (make sure you have the appropriate Windows mapping/trust set up):

$myPI = Connect-PIDataArchive -PIDataArchiveMachineName DNG-PI2012

 

$myPI.Connected should return true. We have successfully connected to our PI Data Archive!

 

Getting some values

Next, let’s try to get some values from a PI tag. Let’s see what are the available cmdlets which contain the word “value” as the noun:

Get-Command -Noun *value* -Module OSIsoft.PowerShell

 

CommandType     Name                                          ModuleName

-----------               ----                                               ----------

Cmdlet               Add-PIValue                                  OSIsoft.PowerShell

Cmdlet               Get-PIValue                                  OSIsoft.PowerShell

Cmdlet               Remove-PIValue                            OSIsoft.PowerShell

Ah! Let's use Get-PIValue. (Don't forget to use Get-Help to look for examples if needed!)

 

One way to get the current value of sinusoid:

Get-PIValue -PointName sinusoid -Connection $myPI -Time ([DateTime]::Now)

 

AnnotationHandle :     0

Value                  :     89.76907

TimeStamp          :     7/20/2015 5:45:40 PM

WriteMode           :      NoReplace

UpdateType         :      None

Annotation           :

IsServerError        :      False

IsQuestionable     :      False

IsSubstituted        :      False

IsAnnotated          :      False

IsGood                :      True

StreamId              :      1

Notice that the TimeStamp is displayed as UTC time.

 

As another example, let's get the list of compressed values of sinusoid between 8 am - 5 pm on 7/19/2015:

Get-PIValue -PointName sinusoid -Connection $myPI -StartTime "19-Jul-2015 8:00:00" -EndTime "19-Jul-2015 17:00:00" 

 

To format the results into an easily viewable format, you can append

  • Format-Table; or
  • Select-Object -Property TimeStamp, Value (or Select TimeStamp, Value); etc.

at the end of the command. E.g.

Get-PIValue -PointName sinusoid -Connection $myPI -StartTime "19-Jul-2015 8:00:00" -EndTime "19-Jul-2015 17:00:00" | Select TimeStamp, Value

 

TimeStamp                                                                                                         Value

---------                                                                                                                 -----

7/19/2015 12:37:23 PM                                                                                         0.9707313

7/19/2015 1:42:23 PM                                                                                             3.3811

7/19/2015 2:56:23 PM                                                                                           23.64582

7/19/2015 5:30:23 PM                                                                                            85.4732

7/19/2015 6:39:23 PM                                                                                           99.19289

7/19/2015 7:44:23 PM                                                                                           96.29641

7/19/2015 8:57:23 PM                                                                                            75.9825

Notice that only the TimeStamp and Value columns are display. Again, TimeStamps are in UTC time (I am at UTC-4 right now).

 

Writing some values

Recall that there is a Add-PIValue cmdlet that looks to be suitable for writing values to PI tags.

 

To add a value of 10 to a tag TestTag at current time:

Add-PIValue -PointName TestTag -Value 10 -Connection $myPI

 

By default, the write mode of "append" is used" unless specified otherwise with the -WriteMode parameter. In addition, you can specify the timestamp, or write different values to multiple tags at the same time. The Get-Help examples contain detailed information.

 

Conclusion

Even though this version of the PowerShell Tools is a re-write of the vCampus CTP version, a lot of the cmdlets and parameters are similar. If you are interested, I encourage checking out some excellent blog posts in the past about using the PowerShell Tools to carry out different tasks:

 

If you have already upgraded to PI SMT 2015, test out some of the cmdlets! As always, please leave your comments and feedback

 

Notes (as of Aug 2015)

  • For timestamps, PI relative times (e.g. *) cannot be used.
  • Data writes with Add-PIValue cannot be buffered right now. There is an enhancement request in place to allow buffered writes in a future release.

Filter Blog

By date: By tag: