RyanBrown

SDK 101 - Archive Time

Discussion created by RyanBrown on May 9, 2012
Latest reply on May 18, 2012 by RyanBrown

Hi Everyone,

 


Following on from my previous post here is the next in my PI SDK series. In my last post I showed a simple console application that dealt with snapshot values. This post takes a small jump forward and looks at how to deal with archived values. I’ll again wrap this in a simple console app to keep the code as simple as possible.

 

To make it a little more interesting I’ll also explain a small problem that was given to me by a colleague and show you how I went about solving this using the SDK. You might have your own ideas on how you would solve it so let me know.

 

Okay so here is the first application

 

Description:
This is a simple console application where we are going to look at the sinusoid test tag and read some archived values for the tag. Told you it was going to be simple!

 
using System;
using PISDK;

namespace SimpleArchiveRead
{
    class Program
    {
        static void Main(string[] args)
        {
            // All values hard coded for this example but I'm keeping it simple
            PISDK.PISDK sdkObj = new PISDK.PISDK();
            string tag = "sinusoid";
            string start = "*-1h";
            string end = "*";
            Server myServer;

            try
            {
                myServer = sdkObj.Servers["localhost"];
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.ReadKey();
                return;
            }

            if (!myServer.Connected)
            {
                try
                {
                    myServer.Open();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    Console.ReadKey();
                    return;
                }
            }

            // This is where we get the archived values for the point
            PIValues archiveValues = myServer.PIPoints[tag].Data.RecordedValues(start, end);

            // For every archived value retrieved just going to output the timestamp and value
            foreach (PIValue value in archiveValues)
            {
                string valString;

                // Want to handle the value being a COMObject ( Digital State )
                if (value.Value.GetType().IsCOMObject)
                {
                    valString = ((DigitalState)value.Value).Name;
                }
                else
                {
                    valString = value.Value.ToString();
                }

                Console.WriteLine("{0}: {1}", value.TimeStamp.LocalDate.ToString("dd/MM/yyyy H:mm:ss"), valString);
            }

            Console.WriteLine();
            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
        }
    }
}

 

 



For running the above application you will need to add three PI references: OSISoft.PISDK, OSISoft.PISDKCommon, OSISoft.PITimeServer
Okay so I’m sure the above example makes sense – what I’d like to do now is to introduce a problem that I was set to solve. If you are just starting out with the SDK then be worth trying to write your own application to solve it before looking at what I did.

 


So the problem given was that there is a PI server with thousands of tags on it (typical scenario). When administering and testing certain things it is valuable to be able to identify the tags that are changing the most and being archived in a specified time frame. There might be some slick way of doing this but we didn’t find one so using the knowledge of reading archive values I set about creating another console app to solve this issue.

 


The same references are required that was needed for the previous application. Bit more code in this as I wanted to have some kind of error handling. You cold strip all that out if you wanted just to see the relevant PI calls.

 
using System;
using System.Collections.Generic;
using PISDK;
using PISDKCommon;

namespace TagUpdates
{
    // Going to build a generic list of structs later
    struct TagCount:IComparable
    {
        private string tagName;
        private int count;

        public string TagName
        {
            get { return tagName; }
        }

        public int Count
        {
            get { return count; }
        }

        public TagCount(string _tagName, int _count)
        {
            tagName = _tagName;
            count = _count;
        }
        
        // Override the CompareTo so that I can sort my list later by count
        public int CompareTo(TagCount other)
        {
            if (other.count == count)
            {
                return 0;
            }
            else if (other.count < count)
            {
                return -1;
            }
            else
            {
                return 1;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // All these variables will be provided by the user
            string server;
            string start;
            string end;
            int count;
            string tagFilter;

            // If no arguments are specified then going to have to ask some questions
            if (args.Length == 0)
            {
                Console.WriteLine();
                
                Console.Write("Specify server name (default: localhost): ");
                server = Console.ReadLine();
                if (server == "")
                    server = "localhost";
                
                Console.Write("Specify start time (default: *-1d): ");
                start = Console.ReadLine();
                if (start == "")
                    start = "*-1d";

                Console.Write("Specify end time (default: *): ");
                end = Console.ReadLine();
                if (end == "")
                    end = "*";

                Console.Write("Specify tag filter (default *): ");
                tagFilter = Console.ReadLine();
                if (tagFilter == "")
                    tagFilter = "*";

                Console.Write("Specify number of results to return: (default 10) ");
                try
                {
                    string cnt = Console.ReadLine();

                    if (cnt != "")
                    {
                        count = Convert.ToInt32(cnt);
                    }
                    else
                    {
                        count = 10;
                    }
                }
                catch (Exception)
                {
                    PrintHelp("Valid number required for the count");
                    return;
                }

                // So going to output the full command so that they can copy later for scripting if needed
                Console.WriteLine();
                Console.WriteLine("Full command is: TagUpdates {0} {1} {2} {3} {4}", server,start, end, count, tagFilter);
                Console.WriteLine();

            }
            else if (args.Length < 5)
            {
                PrintHelp("Not provided enough arguments.");
                Console.ReadKey();
                return;
            }
            else
            {
                // Provided command with arguments already specified
                server = args[0];
                start = args[1];
                end = args[2];
                tagFilter = args[4];

                try
                {
                    count = Convert.ToInt32(args[3]);
                }
                catch (Exception)
                {
                    PrintHelp("Valid number required for the count");
                    Console.ReadKey();
                    return;
                }
            }

            // Got all the information needed. Lets go get the counts
            OutputTags(server, start, end, count, tagFilter);

            Console.WriteLine();
            Console.WriteLine("Press any key to exit");
            Console.ReadKey();

        }
        
        // If any errors are encountered then just output some help along with the error message
        private static void PrintHelp(string _messsage)
        {
            Console.WriteLine();
            Console.WriteLine("************************************************");
            Console.WriteLine(_messsage);
            Console.WriteLine("************************************************");
            Console.WriteLine();
            Console.WriteLine("Format of the command is as follows:");
            Console.WriteLine("\"TagUpdates [serverName] [startTime] [endTime] [resultCount] [tagFilter]\"");
            Console.WriteLine();
            Console.WriteLine("Example: TagUpdates localhost *-1d * 10 sin*");
            Console.WriteLine("Command above will return the top 10 changing tags on local host over one day");
            Console.WriteLine();
            Console.WriteLine("You can also run the command without specifying any arguments.");
            Console.WriteLine("This will prompt you for a series of parameters.");
            Console.WriteLine("Once specified the application will run and also output the full command");
        }

        private static void OutputTags(string _server, string _start, string _end, int _count, string _tagFilter)
        {
            // define top level sdk object and variable for piServer
            PISDK.PISDK pi_sdk = new PISDK.PISDK();
            Server piServer;

            try
            {
                // Exception could be thrown if the server is not known
                piServer = pi_sdk.Servers[_server];
                
                if (!piServer.Connected)
                {
                    piServer.Open();
                }
            }
            catch (Exception ex)
            {
                PrintHelp("Problem connecting to PI Server:" + ex.Message);
                return;
            }

            Console.WriteLine("PI Server is now connected.");
            Console.WriteLine();

            // Using the struct here for generic list
            List topCount = new List();

            Console.WriteLine("Tags to process: {0}", piServer.GetPoints("tag = '" + _tagFilter + "'").Count);
            Console.WriteLine();

            DateTime start = DateTime.Now;
            
            // using where clause parameter to limit results gathered from server based on filter provided
            foreach(PIPoint point in piServer.GetPoints("tag = '" + _tagFilter + "'" ))
            {
                TimeSpan timePassed = DateTime.Now - start;

                // Keep user updated every 5 mins of progress if it's taking a while to run
                if (timePassed.Minutes > 4)
                {
                    Console.WriteLine("Processed: {0}", topCount.Count);
                    start = DateTime.Now;
                }

                int count;

                try
                {
                    // returning the count of archived values for this point
                    // How can I use the Async object parameter to make this faster??
                    count = (point.Data.RecordedValues(_start, _end, BoundaryTypeConstants.btAuto)).Count;
                }
                catch (Exception ex)
                {
                    PrintHelp(ex.Message);
                    Console.ReadKey();
                    return;
                }

                topCount.Add(new TagCount(point.Name, count));
            }

            // Calling the custom sort method for the structs stored
            topCount.Sort();

            // Just incase user specified number of results to return which is greater than results gathered
            if (topCount.Count < _count)
            {
                _count = topCount.Count;
            }

            for (int i = 0; i < _count; i++)
            {
                try
                {
                    // Okay dokay... output the results at the end
                    Console.WriteLine("Tag: {0}, Count:{1}", topCount
.TagName, topCount
.Count);
                }
                catch (IndexOutOfRangeException)
                {
                    return;
                }
            }
        }
    }
}

 

 


So that’s it. Above solution worked fine for us and gave us a quick way of getting tags that are changing often. Let me know what you think. I’m particularly interested in ideas on how to make the solution faster. I believe there should have been a way to take advantage of the PIAsynch object so any examples of that would be great.

 


Not decided yet where I’m heading with next posting so if you have any ideas feel free to put forward a suggestion. Failing that I’ll think of a logical step forward for next one.

 


Ryan

Outcomes