Skip navigation
All Places > PI Developers Club > PI Visualization Development > Blog

Sometimes we want to show compass gauge for showing the wind direction by PI Vision.

I found "Canvas Gauge" library, so tried it on PI Vision.

GitHub - Mikhus/canvas-gauges: HTML5 Canvas Gauge (Canvas Gauges library)

(Wind Turbine picture is coming from symbol library. My custom symbol shows only compass gauge)

 

My PI Vision custom symbol code is on this link.

GitHub - kenji0711/CompassGaugeCustomSymbol: PI Vision 2017 custom symbol. It can show compass. the value should be from…

The tag should have 0-360 values and it shows direction.

Please try it and put the comments. Now we don't need to use PI ProcessBook to show the compass gauge.

 

How to use it:

Copy the following files to %PIHOME64%\PIVision\Scripts\app\editor\symbols\ext

sym-compassgauge.js

sym-compassgauge-config.html

sym-compassgauge-template.html

Copy the following files to ext\libraries

fonts.css

gauge.min.js

Copy the file to ext\Icons

compass.svg

There are a lot of users who want a SQC symbol in PI Vision, similarly to what exists in PI ProcessBook.

On the customer feedback site for PI Vision, there is currently 87 votes for such a feature.

If you also want this feature, please vote!

https://feedback.osisoft.com/forums/320517-pi-coresight/suggestions/10745121-sqc-statistical-quality-control-symbol

Until it gets implemented, I tried my best to mimic the PI ProcessBook's SQC symbol.

In particular, PI ProcessBook's SQC symbol displays histogram along with a trend overlay.

I tried to mimic this in PI Vision. (PI Coresight 2016 R2)

First, for displaying the histogram, I used this sample.

 

It can show histogram like following.

The limits (HiHi, LoLo, etc.) are computed using the following analyses and are stored as child attributes.

 

In PI Vision, I created the following display.

The trend contains the source attribute (in blue) and the limits are the dotted lines.

The red dots showing the exceptions that are created by the analysis too. (2017/05/15 Edited as Jerome's comments)

 

If the histogram does not appear, open the debug console and see if you get the following error:

“Too many points in dataset. Number of points is 401 and current limit is 400.“

You will need to either view the data over a smaller timerange or edit the custom symbol's source code.

In the PI Vision's "ext" folder open the sym-amcharts-histogram.js and add in the "Intervals" property as below.

Intervals represents the number of values the symbol will retrieve. Setting intervals to a large number can impact performance.

 

I am by no means an expert in SQC and in the statistics method involved.

If you have suggestions, please add them in the comments below.

 

I worked with Daniel Cho and Jerome Lefebvre  for creating this display.

I created write value custom symbol by PI Coresight 2016.

The specified item was not found.

Since PI Coresight 2016 R2 and this old custom symbol does not work anymore, I created new one. (Edit 2017/05/30 : Changed the code to PI Vision 2017)

Also for writing value, we have a good example on https://pisquare.osisoft.com/community/developers-club/blog/2017/03/17/the-pi-developers-club-pod-uc-2017

Thank you Anna Perry. It works by PI Vision 2017 too.

 

This example calls PI Web API to write value. So you can create custom symbol with PI Web API from now on.

You can download the code from GitHub. (Changed the code to PI Vision 2017)

GitHub - kenji0711/PICoresight2016R2-CustomSymbol-PutValue

 

(In this display I put "250" value to cdt158 tag.) It is possible to write value to AF attribute too if the attribute is from PI tags!

Put the new value to the box and click "Put New Value" text. It uploads the new value to PI data Archive or AF with current timestamp by PI Web API.

It can be hided by right click > Format Symbol

The point is that this code uses PI Web API Batch request.

Copy following 3 files to %PIHOME64%\PIVision\Scripts\app\editor\symbols\ext folder.

Also copy the image (attaced pen.svg) to Scripts\app\editor\symbols\ext\Icons folder.

sym-putvalue.js (Please change var piwebapiaddress = "MachineName"; to your PI Web API machine name. PI Coresight machine contains PI Web API. So it should be PI Vision machine name)

Also a lot of people interested in to register the custom symbol to the specific user.

So I tried the code to check browser user name. if the user is 'KHASHIMOTO' then this custom symbol appears however, the other users open it, it does not shows up if you put the following code on the sym-putvalue.js file.

if (userNamewithoutDomain == 'KHASHIMOTO')
    {
        PV.symbolCatalog.register(definition);
    }

 

sym-putvalue.js

(function (PV) {  
    var userName = PV.IdentityContext.FriendlyUserName.toUpperCase();  
    var index = userName.indexOf("\\",0);  
    var userNamewithoutDomain = userName.substr(index + 1);  
    function symbolVis() { }  
    PV.deriveVisualizationFromBase(symbolVis);  
  
    symbolVis.prototype.init = function (scope) {  
     this.onDataUpdate = dataUpdate; 
     //default value for timestamp
     scope.config.TimeBox = '*';
     function dataUpdate(data) {  
    if(data) {  
        scope.value = data.Value;  
        scope.time = data.Time;  
        if(data.Path){  
         scope.path = data.Path;  
        }  
        if(data.Label) {  
            scope.label = data.Label;  
        }  
    }
     }  


     scope.putvalue = function() {    
            var piwebapiaddress = "khashimotoe6440";    
            //scope.path contains pi:\\servername\tagname or af:\\servername\databasename\element...|attribute    
            var ini = scope.path.substr(0,3);    
            var orgpath = scope.path.substr(3,10000);    
            //To double the backslash -  \\\\servername\\tagname    
            var path = orgpath.replace(/\\|\\/g,"\\\\");    
            if(ini=="pi:"){    
                var urladdress = "\"https://" + piwebapiaddress + "/piwebapi/points?path="+path+"\"";    
            }    
            else if(ini=="af:"){    
                var urladdress = "\"https://" + piwebapiaddress + "/piwebapi/attributes?path="+path+"\"";    
            }    
            //Get text box value    
            var valueboxval = new String(scope.config.ValueBox, { "type" : "text/plain" });
            var timeboxval = new String(scope.config.TimeBox, { "type" : "text/plain" });    
            //Create value contents as json    
            var jsonval = '"{Timestamp : \'' + timeboxval + '\', Value:\'' + valueboxval + '\'}"';
            //var jsonval = '"{Value:' + valueboxval + '}"';    
            // Create contents of PI Web API batch request
            var contents = '{"GetWebID":{"Method": "GET","Resource": '+ urladdress + '},"WriteValuetoPI":{"Method":"POST","Resource": "{0}","Content":' + jsonval + ',"Parameters": ["$.GetWebID.Content.Links.Value"],"ParentIds": ["GetWebID"]}}';    
    
            //PI Web API Request    
            var batchurl = "https://" + piwebapiaddress + "/piwebapi/batch";
            var xhr= new XMLHttpRequest();    
            //true = Async call    
            xhr.open("POST",batchurl,true);    
            //Set credential for Kerberos    
            xhr.withCredentials = true;    
            xhr.setRequestHeader('Content-Type','application/json');    
            //Send request    
            xhr.send(contents);    
              
        };    
  };  
    var definition = {  
        typeName: 'putvalue',  
        iconUrl:'/Scripts/app/editor/symbols/ext/Icons/pen.svg',  
        datasourceBehavior: PV.Extensibility.Enums.DatasourceBehaviors.Single,  
        visObjectType: symbolVis,  
        getDefaultConfig: function() {  
            return {  
                DataShape: 'Value',  
                Height: 170,  
                Width: 180,  
            TextColor: 'rgb(255,255,255)',  
         ShowLabel: true,  
         ShowTime: true,  
         ShowPutValue: true  
            };  
        },  
    configTitle: 'Format Symbol',  
    StateVariables: [ 'MultistateColor' ]  
    };
    PV.symbolCatalog.register(definition);  
})(window.PIVisualization);  

 

sym-putvalue-config.html

<div class="c-side-pane t-toolbar">
    <span style="color:#fff; margin-left:15px">Text Color</span>
</div>
<div class="config-option-format"><cs-color-picker id="textcolor" ng-model="config.TextColor"></cs-color-picker></div>
<div class="c-side-pane t-toolbar">
    <span style="color:#fff; margin-left:15px">Background Color</span>
</div>
<div class="config-option-format"><cs-color-picker id="backgroundcolor" ng-model="config.BackgroundColor"></cs-color-picker></div>
<div class="c-side-pane t-toolbar">
    <span style="color:#fff; margin-left:15px">Show Options</span>
</div>
<div class="c-config-content">Show Label:
    <input type="checkbox" ng-model="config.ShowLabel">
</div>
<div class="c-config-content">Show Time:
    <input type="checkbox" ng-model="config.ShowTime">
</div>
<div class="c-config-content">Show PutValue:  
    <input type="checkbox" ng-model="config.ShowPutValue">  
</div>  

 

sym-putvalue-template.html

<div ng-style="{background: config.BackgroundColor, color: MultistateColor || config.TextColor}">  
    <div ng-show="config.ShowLabel">{{label}}</div>  
    <div>{{value}}</div>  
    <div ng-show="config.ShowTime">{{time}}</div>  
    <div id='putvalue' ng-show="config.ShowPutValue">  
        Enter Timestamp :<br>
        <input type="text" style="margin-left:5px; width:120px;" name="timebox" ng-model="config.TimeBox" /><br>  
        Enter new Value :<br>
        <input type="text" style="margin-left:5px; width:120px;" name="valuebox" ng-model="config.ValueBox" /><br>  
        <a name='putvalue' type="button" ng-click="putvalue()">Put New Value</a><br>  
    </div>  
</div>  

For using PI Web API from custom symbols, I needed to change CoresMethods = *, CorsHeaders = *, CorsOrigins = * and CorsSupportsCredentials = True in the AF configuration database. (I use Kerberos as Authentication Methods)

Enjoy the write value to PI from PI Vision!

Some customers want to achieve PI WebParts (PI TreeView + PI Graphic) behavior by PI Coresight 2016 R2.

 

For showing Tree, we can use PI Web API to achieve it. Help file contains it.

https://techsupport.osisoft.com/Documentation/PI-WEB-API/help/getting-started.html

Please check following part.

--

A Simple Application

In this section, we'll develop a JavaScript application for exploring the AF hierarchy of your PI System. A basic knowledge of JavaScript, HTML, and CSS is assumed. The following is the complete code.

--

It can shows like PI Tree View.

For showing PI ProcessBook Display by PI Coresight, we can use HTML's Frame.

I created two files. (Please change F2's src= part as your PBDisplay)

1 index.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
    <frameset cols='20%,80%' id="main">
        <frame name="F1" src=".\AFTree.html" />
        <frame name="F2" src="https://CoresightServerName/Coresight/PB/#/PBDisplays/40102?HideToolBar">
        <noframes>
            <body>
                <p>The browser does not support frames</p>
            </body>
        </noframes>
    </frameset>
</html>

 

2 AFTree.html  (I changed it from PI WebAPI's help file. It contains URL hyper link to "F2" frame. Please change RootElement and DisplayPath)

<!DOCTYPE html>
<html>
<head>
    <title>AF Hierarchy Viewer</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
    <script type="text/javascript">
        //Please enter tree's root element's WebID address.
        var RootElement = 'https://PIWebAPIServerName/piwebapi/assetdatabases/D0c_UDU9Pb-k6nVsZu0iuPpgKZD5gy4Ww0WeCfOWEZoj_wS0hBU0hJTU9UT0U2NDQwXFBJIEJJRyBUSVJFUyBDTw/elements';
        //Display path and CurrentElement
        var DisplayPath = 'https://CoresightServerName/Coresight/PB/#/PBDisplays/40102?HideToolBar&CurrentElement=';
        var childrenMap = {
          Elements: ['Elements']
        };
       
        function node(name, type, links, parentDiv, path) {
          this.type = type;
          this.links = links;
          this.flipper = $('<span class="flipper">+</span>').click(flip.bind(this, this));
         
          parentDiv.append(this.flipper).append('<span class=" + type + "><a href="'+DisplayPath + path  + '" target="F2">' + name + '</a></span><br />');
          this.div = $('<div></div>').hide().appendTo(parentDiv);
        }
       
        function loadChildren(n) {
          n.loaded = true;
          childrenMap[n.type].forEach(function(childCollection) {
            $.get(n.links[childCollection], function(collection) {
              n[childCollection] = collection.Items.map(function (item) {
                return new node(item.Name, childCollection, item.Links, n.div, item.Path);
              });
            });
          });
        }
       
        function flip(n) {
          if (!n.loaded) { loadChildren(n); }
          n.flipper.html(n.flipper.html() == '+' ? '-' : '+');
          n.div.toggle();
        }
       
        $(function() {
          root = new node('Elements', 'Elements',
            { Elements: RootElement }, $("#root"));
        });
    </script>
    <style type="text/css">
        div {
          left: 10px;
          position: relative;
        }
        .flipper {
          cursor: pointer;
        }
    </style>
</head>
<body>
  <div id="root"></div>
</body>
</html>

 

For showing PI Coresight in Frame, it needs web.config change.

%pihome64%\Coresight\web.config

<add name="X-Frame-Options" value="Allow"/>

 

You can run it from local but we can use IIS to host these files.

Create new IIS site and put these 2 files (index.html, AFTree.html) to the website's folder. (Click Explorer)

Put 2 files.

 

Now we can see element relative ProcessBook display + Tree.

 

If I click Philly > PHI.Press03, it shows PHI.Press03 display.

IIS's windows authentication is required. Sometimes, IIS gets an error for connecting PI Web API. Annonymous works but if you want to use Kerberos, you need to cover security issues includes SPN etc...

(PI Web API's Kerberos authentication is required for running PI Web API Crawler correctly. This is a tricky part. If PI Web API Crawler does not work, PI Coresight could not show search results.)

 

For showing PI Coresight native display + Tree, we have Asset parameter.

https://livelibrary.osisoft.com/LiveLibrary/content/en/coresight-v8/GUID-C643F092-EB07-41EC-8DC8-5981BF2692F4

PI Coresight 2016 R2 works fine by asset parameter. So you can use Native PI Coresight display + Tree by PI Coresight 2016 R2. (PI Coresight 2016 had an issue for asset parameter)

 

I have created Japanese version too.(日本語はこちら)

PI WebParts (PI TreeView + PI Graphic) の挙動をPI Coresight 2016 R2で再現させる方法

Changing the appearance

Do you want to change what appears in the PI Coresight browser tab?

to

You can change the title "PI Coresight" to anything you want. In this case, I changed it to: "Kenji Coresight".

There are three places where this can be done:

 

1. For the Homepage:

%PIHOME64%\Coresight\Views\Home\Index.cshtml

ViewBag.Title = "PI Coresight";

 

2. For a ProcessBook Page:

%PIHOME64%\Coresight\Scripts\app\pbviewer\pbviewer.navigation.js

 

function setTitle(displayName) {
        var title = 'PI Coresight';

 

3 PI Coresight Display:

%PIHOME64%\Coresight\Scripts\app\editor\display\coresight.display-controller.js

     function setTitle(displayName) {
            var title = 'PI Coresight';

 

For changing mobile version of tab appearance,

%PIHOME64%\Coresight\Views\M\Index.cshtml

<title>PI Coresight</title>
<div data-role="page" data-title="PI  Coresight" id="mostrecent" class="appBackground">
<div data-role="page" data-title="PI Coresight" id="search" class="appBackground">
<div data-role="page" data-title="PI Coresight" id="csdisplay" class="appBackground" data-add-back-btn="true">
<div data-role="page" data-title="PI Coresight" id="pbdisplay" class="appBackground" data-add-back-btn="true">
<div data-role="page" data-title="PI Coresight" id="element" class="appBackground unselectable" data-add-back-btn="true">
<div data-role="page" data-title="PI Coresight" id="trend" class="appBackground unselectable" data-add-back-btn="true">
<div data-role="page" data-title="PI Coresight" id="servicesandtools">

 

You can change the site's favicon as well:

%PIHOME64%\Coresight\favicon.ico

You can change Home logo too.

%PIHOME64%\Coresight\Images\logo.png

to

If you want to change Help,

%PIHOME64%\Coresight\Scripts\app\editor\layout\coresight.cs-header-directive.js

If you comment out following, it delete PI Live Library in Help.

{ id: 'HelpMenuOnLine', title: CS.Messages.HelpMenuOnLine, url: 'https://livelibrary.osisoft.com/LiveLibrary/content/' + lang + '/coresight-v7/GUID-7EF650C5-1235-4F8A-AC61-2EB1D2A3A5BF ' },

to

Or I recommend to delete this question itself if you don't need it.

 

Do you want that users are not possible to create a new display?

Delete div class="c-new-display" in %PIHOME64%\Coresight\Scripts\app\editor\layout\coresight.cs-header-directive.js

You can delete all of the followings.

<div class="c-new-display t-display-content-font">
            <div class="c-icon-display" ng-click="navCtrl.newDisplay()" title="{{::newDisplayTooltip}}">
                <div class="c-new-display-rollover">
                    <div>
                        <svg id="new-display-icon" class="t-icon-fill" version="1.1" x="0px" y="0px"
                             width="25px" height="25px" viewBox="0 -4 25 25" enable-background="new 0 0 25 25" xml:space="preserve">
                        <path d="M9.677,0.823c-5.12,0-9.271,4.188-9.271,9.354c0,5.169,4.151,9.356,9.271,9.356
                                    c5.119,0,9.271-4.188,9.271-9.356C18.948,5.012,14.796,0.823,9.677,0.823" />
                        <polygon fill="#091d3a" points="15.553,8.831 10.982,8.831 10.982,4.114 8.37,4.114 8.37,8.831 3.8,8.831 3.8,11.527 8.37,11.527
                                    8.37,16.243 10.982,16.243 10.982,11.527 15.553,11.527" />
                        </svg>
                    </div>
                    <div class="t-font-color-white" style="text-decoration: none">{{::newDisplayLinkText}}</div>
                </div>
            </div>
        </div>

Then all users could not see/press "New Display" link anymore.

However, you also could not press New Display button anymore.

In this case, luckily we can create new display by this URL.

https://ServerName/coresight/#/Displays/New/

 

Do you want to force "?mode=kiosk" to all users/all displays?

You can do it by comment out if sentence as following.

%PIHOME64%\Coresight\Scripts\app\editor\display\coresight.displays-controller.js

// if (parms.mode === 'kiosk')
setKioskMode(true);

If you do this, all users/ all displays can be seen as Kiosk mode.

We could not choose which users can see display without kiosk. So if you want to create/edit display, you need to change comments out again. (Found with Jerome Lefebvre)

 

PI Coresight 2016 Value object shows tool tip. I don't want to show it.

%PIHOME64%\Coresight\Scripts\app\editor\symbols\coresight.sym-value.js

//valueLabel.path = data.Path;

If you comment out this, it doesn't show tool tip anymore.

By default, Value object's Multi-State changes background color.

I want to change font color. (Not background color)

In this case, change sym-value-template.html's "Fill ||" place.

%PIHOME64%\Coresight\Scripts\app\editor\symbols\sym-value-template.html

<div  title="{{runtimeData.valueLabel.path.indexOf(':') === -1 ? runtimeData.valueLabel.path : runtimeData.valueLabel.path.split(':')[1]}}" style="white-space:nowrap;text-align:left;overflow:visible;" ng-style="{background: (config.Fill), 'font-size': fontSize}" ng-class="{'blink': Blink}">
    <div class="text-symbol-sizing-line" style="display:table" ng-show="config.ShowLabel">
        <span style="margin-right:2px" ng-style="{color:Fill ||config.Stroke}" ng-bind="runtimeData.valueLabel.displayName"></span>
    </div>
    <div class="text-symbol-sizing-line" style="display:table">
        <span ng-style="{color:Fill ||config.ValueStroke}" ng-bind="value"></span>
        <span style="font-size:smaller;margin-left:1px;" ng-style="{color:Fill ||config.Stroke}" ng-bind="units" ng-show="config.ShowUOM"></span>
    </div>
    <div class="text-symbol-sizing-line" style="display:table" ng-show="config.ShowTime">
        <span ng-style="{color:Fill ||config.Stroke}" ng-bind="time"></span>
    </div>
</div>

So I could change font color instead of background color by Multi States.

For changing display's background color, I have checked the display object name by Chrome + F12.

t-display-container is the name of the class.

%PIHOME64%\Coresight\Content\css\theme.base-colors.css

contains the t-display-container.

.t-display-container {
  background-color: white; }

I could see white background color.

PI Coresight 2016 can use custom symbols which is great feature.

GitHub - osisoft/PI-Coresight-Custom-Symbols: Learn how to add a custom symbol, created with JavaScript and HTML, to PI …

by default, it shows icon.

For this, you can take a screenshot of the objects and save it as png/jpg. (Need to change background color though. You can set "#48586b" color as back group color and take screenshot)

Put the image to %PIHOME64%\Coresight\Images folder.

Open the js fiile and put the IconURL.

var defintion = {
typeName: 'liquidgauge',
iconUrl: 'Images/liquid.png',

It shows the icon.

If you click the item, then you can realize that the image contains the background color.

Of cause it is good to create image without background color. Though in this way, it is easy to create the image by ms-paint.

Third party applications are also good options. For example:

https://inkscape.org/en/

 

I have not checked all items. But it seems like HTML5 works fine for changing a lot of things.

Please remember that those changes are not supported officially.

 

Worked with Jerome Lefebvre

This post is related with many questions we receive were people wants to know how to manipulate PI ProcessBook DataSets Dynamically.

Here I am specifically providing an example for ODBC Datasets.

 

Here are few more  remarks if you push this example further yourself I would recommend that

  • You do not create too much datasets as this can impact performances.  ThisDisplay.DataSets.Remove() method can help.
  • After you are done with your dataset, you should either remove it with code (ThisDisplay.DataSets.Remove()) or use the same dataset(s) and modify them programmatically, is use this second approach in the example here.
  • You specify a time period when querying data in your SQL data source so you don't retrieve unecessary data. Something like this:
    • SELECT time,value from DataTable WHERE ValueName='ValueA' and time=>'2015-03-11' and time<'2015-03-12'

 

The following manual, in addition with Processbook help may also help:

PI ProcessBook VBA Language Reference

 

Edit: I have attached a T-SQL script to create and table and populate it with random SQL data.   If you are not sure about what it does, don't run it!

Edit: Updated the example to add a GetDataSet statement when creating a new DataSet as per Eugene Lee's comment.

 

To test this example, just add a trend and 3 buttons on the display.

Then copy the code below in a module.

Assign methods: ValueA, ValueB and ValueC to the buttons.  So when you press the button the sub is called.

Finally, you will need to create a DSN to your database and change the SQL queries that are hard coded in ValueA,ValueB and ValueC methods.

2015-03-12_13-02-02_PI ProcessBook - [Dynamic ODBC Dataset.PDI_].png

 

'---------------------------------------------------------------------------------------
' Module    : Module1
' Purpose   : Examples that shows how to manipulate the ODBC dataset dynamically
'             to update a trend.
'             This example requires 3 buttons to call ValueA,ValueB and ValueC Methods.
'             It also requires a trend called Trend1 (default), and an existing DSN (created with odbcad32 command)
'             In this example DSN is called localSQLServer, just change DSN_Connection_Name constant to modify it.
'             It uses a single PB dataset, that is updated to change the values showed on the trend.
'             With you SQL query, it is important to return the value with name "value", this is why in the query
'             you see value as value, you may change it for data as value for example, this depends on your table structure of course.
'---------------------------------------------------------------------------------------


Const DSN_Connection_Name = "localSQLServer"
Const PBDataSetName = "SQL_ODBC_Dataset"


Sub ValueA()
    Dim query As String
    query = "SELECT time, value as value FROM TimeSeriesData where Tag='ValueA'"
    ChangeDataSet query, "ValueA"
End Sub


Sub VAlueB()
    Dim query As String
    query = "SELECT time, value as value FROM TimeSeriesData where Tag='ValueB'"
    ChangeDataSet query, "ValueB"
End Sub


Sub ValueC()
    Dim query As String
    query = "SELECT time, value as value FROM TimeSeriesData where Tag='ValueC'"
    ChangeDataSet query, "ValueC"
End Sub


' this is the main function that : Create or modify the dataset; updates the trend.
Private Sub ChangeDataSet(query As String, DataSetDescription As String)

    On Error GoTo Error


    Dim oOdbcDataset As dataset

    ' if this fails, it resumes to the next statement, and oOdbcDataset will be nothing
    Set oOdbcDataset = ThisDisplay.Datasets.GetDataset(PBDataSetName)


    ' the dataset does not exist, it needs to be created
    If oOdbcDataset Is Nothing Then
        Set oOdbcDataset = ThisDisplay.Datasets.Add(PBDataSetName, oOdbcDataset, True, 1, True, pbDatasetODBC)
        Set oOdbcDataset = ThisDisplay.Datasets.GetDataset(PBDataSetName)
    End If


    ' modify the dataset - we use the description to provide the displayed value name on the trend.
    ' this requires to modify the Trend properties: Display format -> Legend -> uncheck tagname and check desription instead.
    With oOdbcDataset
        .DataSourceName = DSN_Connection_Name
        .query = query
        .Description = DataSetDescription
    End With
    ThisDisplay.Datasets.SetDataset oOdbcDataset


    ' we clear the trend a recreate the trace(s)
    ' this is required to have the description updated.
    ClearTrendTraces ThisDisplay.Trend1
    ThisDisplay.Trend1.AddTrace PBDataSetName & ".Value"
    Exit Sub
Error:
    If Err.Number = -2147213132 Then
        ' Data Set was not found, we just overcome this error if it occurs
        ' the object oOdbcDataset will be empty so we manage that with the statement: If oOdbcDataset Is Nothing
        Resume Next


    Else
        MsgBox Err.Number & " - " & Err.Description, vbInformation, "Error"
    End If
End Sub


' Clears all traces on a trend
Public Sub ClearTrendTraces(t As Trend)
    For i = 1 To t.TraceCount
        ThisDisplay.Trend1.RemoveTrace i
    Next
End Sub



 

Hope this helps.

 

Best Regards

 

Patrice

Today I was working on a question someone asked me, this person had created a wind turbine animation in PI ProcessBook but he could not figure out how to "Run" his animation.

 

So the question is how could we do that in the most effective way?

So certainly, you can call your animation in the Display_DataUpdate method, it is executed every five seconds.  But this is not suitable if you need more control over the refresh rate.

 

We need an event mechanism, and this is not so easy because VBA does not contain any delegates or fancy timer objects.  I also want this solution to stay as simple as possible.

 

So first I started looking at existing posts on the subject, thanks to David Hollebeek that made the suggestion about the Application.OnIdle event that resides within the ProcessBook VBA Model.

That gave me an idea to create my own timer class in ProcessBook.  I firstly implemented the Application.OnIdle Event, but after few tests, it turned out that performance was very bad and was consuming a lot of CPU.

 

So I have finally written my own Timer class. This class contains a waiting Loop that is combined with a DoEvents and a Sleep.  DoEvents lets ProcessBook update the User Interface (UI), so be sure that you do not remove it! Otherwise ProcessBook will seems to hang, the only way to get it back it to hit Ctrl+Brk.  The "Sleep 25" limits the CPU usage by giving time to the CPU so everything runs smoothly without high CPU usage.

 

To use the class, copy and paste the following code into a new Class in PI ProcessBook Visual Basic Editor:

 

'---------------------------------------------------------------------------------------
' Class    : clsTimer1
' Date      : 2014-12-16
' Purpose  : Class for PI Processbook, to create a timer
'---------------------------------------------------------------------------------------


Private lastTrigger As Single
Private timerPeriod As Single
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private isRunning As Boolean


'---------------------------------------------------------------------------------------
' Procedure : Start
' Purpose  : Start the timer and defines at which interval it ticks
'---------------------------------------------------------------------------------------
'
Public Sub Start(TimeInMilliseconds As Single)
    timerPeriod = TimeInMilliseconds
    isRunning = True
    RunTimer
End Sub


'---------------------------------------------------------------------------------------
' Procedure : Stop
' Purpose  : Stops the timer
'---------------------------------------------------------------------------------------
'
Public Sub StopTimer()
    isRunning = False
End Sub


'---------------------------------------------------------------------------------------
' Procedure : OnTimer
' Purpose  : This is the function called when the timer reaches the configured duration
'            You can manually change what is called here
'
'            To create a timer that calls another method,
'            you need to create a new clsTimer. (unfortunately there is no easy way for delegation in VBA... )
'---------------------------------------------------------------------------------------
'
Private Sub OnTimer()
  ModAnimation.ExecuteAnimation1
End Sub


'---------------------------------------------------------------------------------------
' Procedure : RunTimer
' Purpose  : Loop, wait and fires OnTimer when duration has elapsed...
'---------------------------------------------------------------------------------------
'
Private Sub RunTimer()


    ' infinite loop
    Do While isRunning


        If ((Timer - lastTrigger) * 1000 >= timerPeriod) Then
            lastTrigger = Timer
            Call OnTimer
        End If
     
        ' refreshes the UI
        DoEvents
   
        ' add a small pause in the loop so CPU does not goes up...
        Sleep 25
   
    Loop


End Sub








 

So we have our Timer class, now to use it I chose to create a Module, my Module would contain all my animations. I also have added two methods: StartAnimation and StopAnimation, that I can call with a button to start and stop the animation.

 

Create a new Module called ModAnimation  and paste this code into it.

You will need to change, at least the line 37, depending on which object(s) you need to animate, and add more objects depending of the complexity of your animation.

 

Option Explicit


Private animation1Timer As clsTimer1


Const ANIMATION_SPEED = 500 ' speed in milli seconds


Public Sub StartAnimation()


    If animation1Timer Is Nothing Then
        Set animation1Timer = New clsTimer1
        animation1Timer.Start ANIMATION_SPEED
    End If


End Sub


Public Sub StopAnimation()
    animation1Timer.StopTimer
    Set animation1Timer = Nothing
End Sub




Public Sub ExecuteAnimation1()
  
    ' static value remembers the value between sub calls...
    Static i As Integer
      
    ' do the animation(s)
    ' this is a very simple example...
    ThisDisplay.Symbols.Item("OSILogo").Rotation = i
  
    ' rotation
    i = i + 1
    If i > 359 Then i = 0
                    
End Sub


 

 

In my example, the module makes the following image rotate, this is a very simple example.

But you can easily change the content of ExecuteAnimation1 to create your own animation.

2014-12-16_16-31-50_PI ProcessBook - [ProcessBookAnimationTimer.pdi_].png

 

 

I hope this may help you with PI ProcessBook, when you need to create your own events and control when they occurs.  Also let me know what you think and if you have other ideas to improve this solution.

 

A working example is also attached to this post.