As a way to test the Extensibility CTP, I thought it would be interesting to attempt to mimic the functionality provided by the PI ProcessBook playback control in a PI Coresight tool window. In this post, I will walk you through my process of creating the tool window and hopefully spark some ideas for creating your own.

 

What is the PI ProcessBook playback control?

This is a tool that allows you to replay your data stream as it happen, much like playing a video. It allows you to control the start time, end time, refresh rate, and interval.

 

The first step is to create the implementation file for the tool and the basic JavaScript structure. The file should be named tool-playback.js and saved under the INSTALLATION_FOLDER\Scripts\app\editor\tools\ext directory. To create the basic structure, we will create an IIFE and create our definition object and register it with the PI Coresight toolCatalog using the register function.

 

(function (CS) {
     'use strict';  
    
     var def = {
          typeName: 'playback',
          displayName: 'Playback',
          iconUrl: 'Images/chrome.custom_addin_crossed_tools.svg'
     };  

     CS.toolCatalog.register(def);  
  
})(window.Coresight); 

 

From a high level, there are certain things that our tool will need to be capable of. It will need to be able to start and stop the playback, as well as, having input for how much it should increment, how fast it should increment and how many times it should run the increment.

 

In order to begin creating and initializing these settings, we will have to add an init function to our definition and create the function.

 

(function (CS) {  
   'use strict';  

   var def = {
      typeName: 'playback',
      displayName: 'Playback',
      iconUrl: 'Images/chrome.custom_addin_crossed_tools.svg',
      init: init
   };  

   function init(scope, elem) {
   }

   CS.toolCatalog.register(def);  

})(window.Coresight); 

 

Next, in our initialization function we will create the stubs for the start and stop functions and create the initialization of the input options.

 

(function (CS) {  
   'use strict';  

   var def = {
      typeName: 'playback',
      displayName: 'Playback',
      iconUrl: 'Images/chrome.custom_addin_crossed_tools.svg',
      init: init
   };  

   function init(scope, elem) {

      scope.options = {
         numberOfIncrements: 24,
         increaseIncrementTime: '+1h',
         incrementTimer: 500
      };

      scope.start = function() {
      };

      scope.stop = function() {
      };  
   }

   CS.toolCatalog.register(def);  
  
})(window.Coresight); 

 

At this point, it's a good idea to set up our interface and test the basic functionality of our tool.

 

 

The UI for the tool pane is very simplistic. It has three inputs for each of the options and two buttons for starting and stopping. For each of the inputs, we are using ng-model to bind to the correct option that was created in the input function above. For the buttons, we are using ng-click to bind to the functions we created. This file should be named tool-playback-template.html and saved under the INSTALLATION_FOLDER\Scripts\app\editor\tools\ext directory.

 

<div class="c-pane-header t-pane-header-color t-content-font-large t-font-color-white flex-none">Playback</div>
Number of increments: <input type="text" ng-model="options.numberOfIncrements"></input>
Time to jump: <input type="text" ng-model="options.increaseIncrementTime"></input>
Playback speed: <input type="text" ng-model="options.incrementTimer"></input
<button ng-click="start()">Go</button> 
<button ng-click="stop()">Stop</button>   

 

After verifying the basic functionality, it is time to figure out what to do when the user clicks the start button. We know that we want to have a time that fires off at regular intervals. The best way to do this in JavaScript is to use the setInterval function. Because we are in an AngularJS environment it is recommended that we use $interval instead.

 

Looking at the documentation for $interval, we see that the return of the $interval function is a promise that can be used to cancel the interval. This sounds exactly like what we want in a stop function. Below are the start and stop functions that we previously declared in the init function. Since $interval is a service, I will need to use dependency injection to make it available in my tool. This is done by adding an inject parameter to the definition object and a parameter to the init method itself.

 

(function (CS) {  
   'use strict';  

   var def = {
      typeName: 'playback',
      displayName: 'Playback',
      iconUrl: 'Images/chrome.custom_addin_crossed_tools.svg',
      inject: [ '$interval' ],
      init: init
   };  

   function init(scope, elem, $interval) {

      scope.options = {
         numberOfIncrements: 24,
         increaseIncrementTime: '+1h',
         incrementTimer: 500
      };

      // create a variable to hold the return value from $interval for canceling
      var intervalTimer;
      scope.start = function() {
         // call the $interval function with a function that will be executed each time the timer is called
         // the next 2 parameters are how often it should run the function and how many times it should run the function
         intervalTimer = $interval(function() {
         }, scope.options.incrementTimer, scope.options.numberOfIncrements);
      };

      scope.stop = function() {
         // when stop is pressed cancel the interval timer
         $interval.cancel(intervalTimer);
      };  
   }

   CS.toolCatalog.register(def);  

})(window.Coresight); 

 

Now the last major piece of the puzzle, how do we actually set the time range of the display? I am not going to go into major details about how I figured this out, but it can be found by tracing through PI Coresight code. My starting point was assuming the time bar was doing something to set the time range. In reading through that code, I found a service called timeProvider that had a method named requestNewTime that was used in the time bar. [Full disclosure, I am a PI Coresight developer, so I really did know this existed anyway, but this is how I would have tracked it down if I did not.]

 

Now that I have a service, I can inject it into my definition, as I did with $interval, and then call it from the start function. The arguments to the requestNewTime function are the new start and end time. So I will get the current display time, which also happens to be in the timeProvider object, and increment it by the proper amount from our input options.

 

(function (CS) {  
   'use strict';  

   var def = {
      typeName: 'playback',
      displayName: 'Playback',
      iconUrl: 'Images/chrome.custom_addin_crossed_tools.svg',
      inject: [ 'timeProvider', '$interval' ],
      init: init
   };  

   function init(scope, elem, timeProvider, $interval) {

      scope.options = {
         numberOfIncrements: 24,
         increaseIncrementTime: '+1h',
         incrementTimer: 500
      };

      // create a variable to hold the return value from $interval for canceling
      var intervalTimer;
      scope.start = function() {
         // call the $interval function with a function that will be executed each time the timer is called
         // the next 2 parameters are how often it should run the function and how many times it should run the function
         intervalTimer = $interval(function() {
            timeProvider.requestNewTime(
               timeProvider.displayTime.start + scope.options.increaseIncrementTime, 
               timeProvider.displayTime.end + scope.options.increaseIncrementTime,
               true);
         }, scope.options.incrementTimer, scope.options.numberOfIncrements);
      };

      scope.stop = function() {
         // when stop is pressed cancel the interval timer
         $interval.cancel(intervalTimer);
      };  
   }

   CS.toolCatalog.register(def);  

})(window.Coresight); 

 

With all of this done, we have the basics of a play back control. The final part I will discuss here is making it slightly more friendly by enabling and disabling parts of the UI based on whether or not the play back is currently running. To do this, I will add an isRunning variable on the scope, so it can be bound to in html, and update it in the start and stop function accordingly.

 

(function (CS) {  
   'use strict';  

   var def = {
      typeName: 'playback',
      displayName: 'Playback',
      iconUrl: 'Images/chrome.custom_addin_crossed_tools.svg',
      inject: [ 'timeProvider', '$interval' ],
      init: init
   };  

   function init(scope, elem, timeProvider, $interval) {

      scope.options = {
         numberOfIncrements: 24,
         increaseIncrementTime: '+1h',
         incrementTimer: 500
      };

      scope.isRunning = false;

      // create a variable to hold the return value from $interval for canceling
      var intervalTimer;
      scope.start = function() {
         scope.isRunning = true;

         // call the $interval function with a function that will be executed each time the timer is called
         // the next 2 parameters are how often it should run the function and how many times it should run the function
         intervalTimer = $interval(function(count) {
            timeProvider.requestNewTime(
               timeProvider.displayTime.start + scope.options.increaseIncrementTime, 
               timeProvider.displayTime.end + scope.options.increaseIncrementTime,
               true);

            // if we are on the last increment, flip the isRunning flags
            if(count === scope.options.numberOfIncrements) {
                 scope.isRunning = false;
            }
         }, scope.options.incrementTimer, scope.options.numberOfIncrements);
      };

      scope.stop = function() {
         // when stop is pressed cancel the interval timer
         $interval.cancel(intervalTimer);
         scope.isRunning = false;
      };  
   }

   CS.toolCatalog.register(def);  
  
})(window.Coresight); 

 

Finally, update the UI to enable or disable each of the inputs and buttons based on the isRunning state.

 

<div class="c-pane-header t-pane-header-color t-content-font-large t-font-color-white flex-none">Playback</div>
Number of increments: <input type="text" ng-model="options.numberOfIncrements" ng-disabled="isRunning"></input>
Time to jump: <input type="text" ng-model="options.increaseIncrementTime" ng-disabled="isRunning"></input>
Playback speed: <input type="text" ng-model="options.incrementTimer" ng-disabled="isRunning"></input>
<button ng-click="start()" ng-disabled="isRunning">Go</button> 
<button ng-click="stop()" ng-disabled="!isRunning">Stop</button>  

 

EDIT: The code for this blog post has been added to the OSIsoft GitHub page.