Skip to main content
PI System
AF SDK
C#

Traversing AF Elements and Checking PI Point Attribute Mappings Using OSIsoft AF SDK

Learn how to use the OSIsoft AF SDK to traverse AF elements, check PI Point attribute mappings, retrieve data, and handle common errors. We'll cover traversing the entire AF database, checking for attribute mapping errors, retrieving data for the last 10 days, and handling time range issues. Get the total element count programmatically.

Roshan Soni

15 min read

In this blog post, we'll explore how to use the OSIsoft AF SDK to interact with the PI System Asset Framework (AF). Specifically, we'll cover how to:

  • Traverse all elements in an AF database.
  • Check for errors in PI Point attribute mappings.
  • Retrieve values from the last 10 days for PI Point attributes.
  • Correct common coding errors when working with time ranges.
  • Get the total count of elements across the entire hierarchy.

By the end of this post, you'll have a comprehensive understanding of how to programmatically interact with AF elements and attributes, and how to handle common tasks and errors using the AF SDK.

Prerequisites

Before diving in, ensure you have the following:

  • AF SDK Installed: You need the OSIsoft AF SDK installed on your machine. You can download it from the OSIsoft Customer Portal.
  • Development Environment: Visual Studio or any other C# development environment.
  • Access to a PI System: You'll need access to an AF server and database.

Table of Contents

  1. Setting Up the Project
  2. Connecting to the AF Server and Database
  3. Traversing All Elements
  4. Checking PI Point Attribute Mappings
  5. Retrieving Values from the Last 10 Days
  6. Handling Time Range Errors
  7. Getting Total Element Count
  8. Conclusion

Setting Up the Project

First, create a new Console Application in Visual Studio.

  1. Create a New Project:

    • Open Visual Studio.
    • Click on File > New > Project.
    • Select Console App (.NET Framework).
    • Name your project (e.g., AFSDKExample).
  2. Add AF SDK Reference:

    • Right-click on your project in the Solution Explorer.
    • Select Manage NuGet Packages.
    • Search for OSIsoft.AFSDK and install it.
    • Alternatively, add a reference to OSIsoft.AFSDK.dll located typically at C:\Program Files (x86)\PIPC\AF\PublicAssemblies\4.0\.

Connecting to the AF Server and Database

Before interacting with AF elements, you need to connect to your AF server and database.

using System;
using OSIsoft.AF;

namespace AFSDKExample
{
    class Program
    {
        static void Main(string[] args)
        {
            // Connect to PI System
            PISystems piSystems = new PISystems();
            string afServerName = "YourAFServerName"; // Replace with your AF Server name
            string afDatabaseName = "YourAFDatabaseName"; // Replace with your AF Database name

            PISystem piSystem = piSystems[afServerName];
            if (piSystem == null)
            {
                Console.WriteLine("AF Server not found.");
                return;
            }

            AFDatabase afDatabase = piSystem.Databases[afDatabaseName];
            if (afDatabase == null)
            {
                Console.WriteLine("AF Database not found.");
                return;
            }

            Console.WriteLine($"Connected to AF Database: {afDatabase.Name}");
        }
    }
}

Explanation:

  • PISystems: Represents a collection of AF servers.
  • PISystem: Represents a single AF server.
  • AFDatabase: Represents an AF database on the server.

Note: Replace "YourAFServerName" and "YourAFDatabaseName" with your actual AF server and database names.

Traversing All Elements

To traverse all elements in the AF database, we'll use recursive methods.

static void TraverseElements(AFElements elements)
{
    foreach (AFElement element in elements)
    {
        Console.WriteLine($"Element: {element.Name}");
        // Recursively traverse child elements
        TraverseElements(element.Elements);
    }
}

In the Main method, call TraverseElements:

TraverseElements(afDatabase.Elements);

Explanation:

  • AFElements: Represents a collection of AFElement objects.
  • AFElement: Represents an element in the AF hierarchy.
  • The method recursively calls itself to traverse child elements.

Checking PI Point Attribute Mappings

To focus on PI Point attributes and check for errors in their mappings:

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

static void CheckPIAttributes(AFAttributes attributes)
{
    foreach (AFAttribute attribute in attributes)
    {
        if (attribute.DataReference != null && attribute.DataReference.Name == "PI Point")
        {
            try
            {
                // Attempt to get the value of the attribute
                var value = attribute.GetValue();

                // Check if the value retrieval succeeded
                if (value.IsGood)
                {
                    Console.WriteLine($"  PI Point Attribute: {attribute.Name}, Value: {value.Value}");
                }
                else
                {
                    Console.WriteLine($"  PI Point Attribute: {attribute.Name}, Error: {value.Status}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"  PI Point Attribute: {attribute.Name}, Exception: {ex.Message}");
            }
        }
    }
}

Update the TraverseElements method to include attribute checking:

static void TraverseElements(AFElements elements)
{
    foreach (AFElement element in elements)
    {
        Console.WriteLine($"Element: {element.Name}");
        CheckPIAttributes(element.Attributes);
        TraverseElements(element.Elements);
    }
}

Explanation:

  • AFAttribute: Represents an attribute of an element.
  • DataReference: Determines how the attribute gets its value (e.g., from a PI Point).
  • We filter attributes where DataReference.Name is "PI Point".

Retrieving Values from the Last 10 Days

To retrieve values for PI Point attributes from the last 10 days:

using OSIsoft.AF.Time;

static void CheckPIAttributes(AFAttributes attributes)
{
    foreach (AFAttribute attribute in attributes)
    {
        if (attribute.DataReference != null && attribute.DataReference.Name == "PI Point")
        {
            try
            {
                // Define the time range for the last 10 days
                AFTime endTime = AFTime.Now;
                AFTime startTime = endTime.LocalTime.AddDays(-10);
                AFTimeRange timeRange = new AFTimeRange(startTime, endTime);

                // Get the recorded values within the time range
                AFValues values = attribute.Data.RecordedValues(timeRange, AFBoundaryType.Inside, null, false);

                if (values != null && values.Count > 0)
                {
                    Console.WriteLine($"  PI Point Attribute: {attribute.Name}, Values:");
                    foreach (AFValue value in values)
                    {
                        if (value.IsGood)
                        {
                            Console.WriteLine($"    Timestamp: {value.Timestamp}, Value: {value.Value}");
                        }
                        else
                        {
                            Console.WriteLine($"    Timestamp: {value.Timestamp}, Error: {value.Status}");
                        }
                    }
                }
                else
                {
                    Console.WriteLine($"  PI Point Attribute: {attribute.Name}, No values found for the last 10 days.");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"  PI Point Attribute: {attribute.Name}, Exception: {ex.Message}");
            }
        }
    }
}

Explanation:

  • AFTime: Represents a point in time.
  • AFTimeRange: Represents a time range between two AFTime instances.
  • RecordedValues: Retrieves recorded values within the specified time range.

Note: We corrected the way we calculate the start time by using endTime.LocalTime.AddDays(-10).

Handling Time Range Errors

Initially, we tried to use AFTime.Now.AddDays(-10), but this method doesn't exist. The correct way is:

AFTime endTime = AFTime.Now;
AFTime startTime = endTime.LocalTime.AddDays(-10);
AFTimeRange timeRange = new AFTimeRange(startTime, endTime);

Explanation:

  • AFTime.Now gets the current time.
  • endTime.LocalTime.AddDays(-10) subtracts 10 days from the local time.
  • We create a new AFTimeRange using the start and end times.

Getting Total Element Count

To get the total number of elements across the entire hierarchy:

static int GetTotalElementCount(AFElements elements)
{
    int count = 0;
    foreach (AFElement element in elements)
    {
        count++;
        count += GetTotalElementCount(element.Elements); // Recursively count child elements
    }
    return count;
}

In the Main method, call GetTotalElementCount:

int totalCount = GetTotalElementCount(afDatabase.Elements);
Console.WriteLine($"Total element count across hierarchy: {totalCount}");

Explanation:

  • We initialize a counter count.
  • For each element, we increment the count and recursively call GetTotalElementCount for its child elements.

Putting It All Together

Here's the complete code incorporating all the above sections:

using System;
using OSIsoft.AF;
using OSIsoft.AF.Asset;
using OSIsoft.AF.Data;
using OSIsoft.AF.Time;

namespace AFSDKExample
{
    class Program
    {
        static void Main(string[] args)
        {
            // Connect to PI System
            PISystems piSystems = new PISystems();
            string afServerName = "YourAFServerName"; // Replace with your AF Server name
            string afDatabaseName = "YourAFDatabaseName"; // Replace with your AF Database name

            PISystem piSystem = piSystems[afServerName];
            if (piSystem == null)
            {
                Console.WriteLine("AF Server not found.");
                return;
            }

            AFDatabase afDatabase = piSystem.Databases[afDatabaseName];
            if (afDatabase == null)
            {
                Console.WriteLine("AF Database not found.");
                return;
            }

            Console.WriteLine($"Connected to AF Database: {afDatabase.Name}");

            // Traverse through all elements
            TraverseElements(afDatabase.Elements);

            // Get total element count
            int totalCount = GetTotalElementCount(afDatabase.Elements);
            Console.WriteLine($"Total element count across hierarchy: {totalCount}");
        }

        static void TraverseElements(AFElements elements)
        {
            foreach (AFElement element in elements)
            {
                Console.WriteLine($"Element: {element.Name}");
                CheckPIAttributes(element.Attributes);

                // Recursively traverse child elements
                TraverseElements(element.Elements);
            }
        }

        static void CheckPIAttributes(AFAttributes attributes)
        {
            foreach (AFAttribute attribute in attributes)
            {
                if (attribute.DataReference != null && attribute.DataReference.Name == "PI Point")
                {
                    try
                    {
                        // Define the time range for the last 10 days
                        AFTime endTime = AFTime.Now;
                        AFTime startTime = endTime.LocalTime.AddDays(-10);
                        AFTimeRange timeRange = new AFTimeRange(startTime, endTime);

                        // Get the recorded values within the time range
                        AFValues values = attribute.Data.RecordedValues(timeRange, AFBoundaryType.Inside, null, false);

                        if (values != null && values.Count > 0)
                        {
                            Console.WriteLine($"  PI Point Attribute: {attribute.Name}, Values:");
                            foreach (AFValue value in values)
                            {
                                if (value.IsGood)
                                {
                                    Console.WriteLine($"    Timestamp: {value.Timestamp}, Value: {value.Value}");
                                }
                                else
                                {
                                    Console.WriteLine($"    Timestamp: {value.Timestamp}, Error: {value.Status}");
                                }
                            }
                        }
                        else
                        {
                            Console.WriteLine($"  PI Point Attribute: {attribute.Name}, No values found for the last 10 days.");
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"  PI Point Attribute: {attribute.Name}, Exception: {ex.Message}");
                    }
                }
            }
        }

        static int GetTotalElementCount(AFElements elements)
        {
            int count = 0;
            foreach (AFElement element in elements)
            {
                count++;
                count += GetTotalElementCount(element.Elements); // Recursively count child elements
            }
            return count;
        }
    }
}

Conclusion

In this blog post, we've covered how to:

  • Traverse AF Elements: Using recursive methods to navigate through all elements in the AF hierarchy.
  • Check PI Point Attributes: Filtering attributes to focus on PI Point data references and checking for errors.
  • Retrieve Values for the Last 10 Days: Using AFTime and AFTimeRange to specify time periods and retrieve historical data.
  • Handle Time Range Calculations Correctly: Understanding how to correctly manipulate AFTime objects.
  • Get Total Element Count: Recursively counting all elements in the database hierarchy.

By understanding these concepts and code examples, you can build robust applications that interact with the PI System AF, automate data retrieval, and perform extensive data analysis.

Next Steps:

  • Expand Error Handling: Implement more detailed error handling and logging mechanisms.
  • Optimize Performance: For large AF databases, consider using asynchronous methods or bulk data retrieval techniques.
  • User Interface: Build a GUI application for better visualization of the data and errors.

References:

Disclaimer: Ensure you have the necessary permissions to access and query your PI System and that you comply with your organization's data access policies.

Tags

#OSIsoft PI
#PI System
#AF SDK
#Error Handling
#Programming
#AF Elements
#Attribute Mappings
#Time Ranges
#C#

About Roshan Soni

Expert in PI System implementation, industrial automation, and data management. Passionate about helping organizations maximize the value of their process data through innovative solutions and best practices.

Sign in to comment

Join the conversation by signing in to your account.

Comments (0)

No comments yet

Be the first to share your thoughts on this article.

Related Articles

Enhancing PI ProcessBook Trends with Banding and Zones: User Needs, Workarounds, and the Road Ahead

A look at the user demand for trend banding/zoning in OSIsoft PI ProcessBook, current VBA workarounds, UI challenges, and how future PI Vision releases aim to address these visualization needs.

Roshan Soni

Migrating PIAdvCalcFilVal Uptime Calculations from PI DataLink to PI OLEDB

Learn how to translate PI DataLink's PIAdvCalcFilVal advanced calculations—like counting uptime based on conditions—into efficient PI OLEDB SQL queries. Explore three practical approaches using PIAVG, PIINTERP, and PICOunt tables, and get tips for validation and accuracy.

Roshan Soni

Understanding PI Web API WebID Encoding: Can You Generate WebIDs Client-Side?

Curious about how PI Web API generates WebIDs and whether you can encode them client-side using GUIDs or paths? This article explores the encoding mechanisms, current documentation, and best practices for handling WebIDs in your applications.

Roshan Soni