Learn the Basics of PI Programming - Building PI AF Elements with PI AF SDK

Version 1

    One of the common uses of PI AF SDK is to automatically build out large, complex databases. In this exercise we take a look at how we can access a PI AF asset hierarchy with PI AF SDK, and make some changes through code. This exercise uses an AF Database that must already be present on your AF Server. Before completing the exercise, you will need to import the database yourself, however if you are in an organised lab, this will be done for you. For details on importing the database, see the attachments to this document.

     

    Disclaimer: This sure looks like one of those tried and true “copy and paste the code snippets” exercises. But… it’s not. When you encounter problems, put your debugging hat on and try to figure out what’s happening.

     

    Exercise Objectives

    • Start a project and reference PI AF SDK
    • Connect to a PI AF Server
    • Read data from a PI AF Server
    • Make changes to the PI AF Database
    • Check in/Check out your changes to the PI AF Database

    Approach

    1.      Open Visual Studio on your development machine.

    2.      Create a new project, and select Visual C# > Console Application.

    3.      Choose a name for the project, and select OK.

    4.      Add a reference to the PI AF SDK assembly to the project:

     

    • Click on Project > Add Reference...
    • Select Assemblies > Extensions.
    • Check OSIsoft.AFSDK. Ensure you check the 4.0 version of the extension.
    • Press OK.

     

    5.     Add the following “using” statements to the top of your code:

     

    using OSIsoft.AF; 
    using OSIsoft.AF.Asset;
    
    
    
    
    
    
    
    

     

    6.     Next, add the following code to your main method to explicitly connect to your PI AF Server and a set AF Database. We’ll be using the “OSIsoft Enterprises” database:

     

    // Connect to the default AF Server and OSIsoft Enterprises Database
    PISystems pisystems = new PISystems();
    PISystem pisystem = pisystems.DefaultPISystem;
    pisystem.Connect();
    AFDatabase OSIsoftEnterprises = pisystem.Databases["OSIsoft Enterprises"];
    Console.WriteLine("Database: {0}", OSIsoftEnterprises.Name);
    
    
    
    
    
    
    
    

     

    7.     Make a breakpoint by clicking in the grey margin next to the “static void…” line, then click the Start button with the green triangle (or from the DEBUG menu, click Start Debugging) to start a debugging session and check your work so far. Step through your code with F10 until you pass the last line. Select the command window to check on progress. A proper solution at this point will have the following output:

    1.png

    8.      Stop debugging with DEBUG > Stop Debugging.

    9.      The OSIsoft Enterprises element hierarchy begins with locations, which contains processes, which contains equipment. Once you’re successfully retrieving a reference to the OSIsoft Enterprises database, add code to retrieve references to the Tucson location, the Distilling Process in Tucson, and the Equipment used in that process:

     

     

    // Retrieve specific elements involved in the distilling process in Tucson
    AFNamedCollectionList<AFElement> tucsons = AFElement.FindElements(OSIsoftEnterprises, null, "Tucson", AFSearchField.Name, true, AFSortField.Name, AFSortOrder.Ascending, 10);
    if (tucsons.Count < 1)
    {
    return;
    }
    AFElement tucson = tucsons[0];
    AFElement distilling = tucson.Elements["Distilling Process"];
    AFElement equipment = distilling.Elements["Equipment"];
    
    
    
    
    
    
    
    

     

    10.     The distilling equipment in Tucson includes a boiler named B-117. Use the reference to the Tucson distilling equipment to change the description for this boiler. In order to modify an element, you must first check it out, then check it back in when your modifications are complete:

     

    // Edit the existing boiler used in the distilling process in Tucson
    AFElement boiler = equipment.Elements["B-117"];
    Console.WriteLine("Boiler Description: {0}", boiler.Description);
    boiler.CheckOut();
    boiler.Description = "Original boiler used in Tucson.";
    boiler.CheckIn();
    Console.WriteLine("Boiler Description: {0}", boiler.Description);
    
    
    
    
    
    
    
    

     

    11.     Click the Start button with the green triangle (or from the DEBUG menu, click Start Debugging) to start a debugging session and check your work so far. A proper solution at this point will have the following output:

    2.png

    Note: You will receive an unhandled exception if directly copying and pasting the above code into your application. Try to debug your code and find the problem, then modify your code to fix it. Only move on from this point when you’ve successfully got the above output. A solution can be found at the bottom if this exercise, if needed.

    12.     Now add a second boiler named B-118 in Tucson. Note that both the new boiler and the Tucson distilling Equipment element need to be checked in. Replace YourName with your name:

     

    // Add another boiler to the Tucson distilling process
    AFElement newBoiler = equipment.Elements.Add("Boiler-118",OSIsoftEnterprises.ElementTemplates["Boiler"]);
    newBoiler.Description = "New boiler to be used in Tucson.";
    newBoiler.CheckIn();
    equipment.CheckIn();
    Console.WriteLine("Boiler Description: {0}", newBoiler.Description);
    
    
    
    
    
    
    
    

     

    13.     Click the Start button with the green triangle (or from the DEBUG menu, click Start Debugging) to start a debugging session and check your work so far. A proper solution at this point will have the following output:

    3.png

    Note: Again, you'll get an unhandled exception without modifying the code above. Try to debug this yourself before checking the solution below.

    14.     Note that there is no difference this time between the description of B-117 before and after it is checked back in, because the new description was already checked-in during a prior debugging session. Press Enter to stop the debugging session and return to Visual Studio.

    15.     Without making any modifications to your source code, immediately start another debugging session, and note that we have an unhandled exception:

    1.png

    16.     Add code to your program to handle this exception. Debug to confirm that your code can handle this scenario properly.

    17.     Save your work.


    Solution:

    You will get an unhandled exception when debugging the code written during step 10 informing that the AFElement boiler has a null reference, and you can't fetch the description of it. When debugging, you will also see that the "equipment" object also has a null reference. This is because your code is trying to find it underneath distilling. Checking the structure in PI System Explorer will reveal that there is no such element as equipment, the boiler is straight under the distilling element. There is also no boiler called "B-117", it's called "Boiler-117".

     

    Remove this line from your code completely:

    AFElement equipment = distilling.Elements["Equipment"];
    
    
    
    
    
    
    
    

     

    Then change this line:

    AFElement boiler = equipment.Elements["B-117"];
    
    
    
    
    
    
    
    

     

    to this:

    AFElement boiler = distilling.Elements["Boiler-117"];
    
    
    
    
    
    
    
    

     

    There are many solutions to the final question, but the simplest is to add an IF statement to check for the boiler before attempting to create it. Replace the code in step 12 that attempts to create the boiler with the following:

     

    // Add another boiler to the Tucson distilling process
    AFElement newBoiler = distilling.Elements[“Boiler-118”];
    IF (newBoiler.name==null)
    {
         newBoiler = equipment.Elements .Add("Boiler-118",OSIsoftEnterprises.ElementTemplates["Boiler"]);
         newBoiler.Description = "New boiler to be used in Tucson.";
         newBoiler,CheckIn();
         distilling.CheckIn();
    }
    Console.WriteLine("Boiler Description: {0}", newBoiler.Description);
    
    
    
    
    

     

    Have you got a better solution? Post it below!