Skip navigation
All Places > PI Developers Club > Blog > 2018 > February



PI Web API 2017 R2 comes with WebID 2.0, which allows you to generate the Web IDs on the client-side, without having to make an HTTP request.


This is actually an important performance improvement since if you wanted to get the current value of a PI Point using older versions of PI Web API, two requests were needed:

  • One request to get the WebID using the path as input.
  • One request to get the current value using the WebID.


By generating the Web ID on the client, only the second request is needed.


Reducing the number of HTTP request is an important step to improve the performance of you application.


The goals of this blog post are:


  • Teach you how to use the PI Web API client library for .NET Standard to generate Web IDs on the client.
  • View the information of a given Web ID.
  • Show you the source code and the unit tests so you can understand the logic and create something similar on other platforms.




I've referred to the articles and videos written and recorded by Christopher Sawyer below to get started.



I strongly suggest taking a look at them before continuing to read.


The WebIdHelper class


A new property called WebIdHelper was added to the PIWebApiClient, the top level object of the client library. This property is a WebIdHelper object with two methods:


  • GetWebIdInfo(string webId)
  • GenerateWebIdByPath(string webId, Type objectType, Type ownerType)



Getting the Web ID information


Getting the information of a given Web ID is very simple. Let's take a look at the AF attribute example:


PIAttribute piAttribute = client.Attribute.GetByPath(Constants.AF_ATTRIBUTE_PATH);
WebIdInfo piAttributeWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAttribute.WebId);


The first line retrieves the Web ID of an AF attribute through a HTTP request (old method). The second line gets the WebID information by returning an object from the WebIdInfo class. This class has the following properties:


  • Marker (string) --> The marker of the object.
  • ObjectID (Guid) --> The ID of the object.
  • ObjectType (Type) --> The type of the object.
  • OwnerID (Guid) --> The ID of the owner of the object.
  • OwnerType (Type) --> The type of the owner of the object.
  • Path (string) --> The path of the object.
  • PointID (int) --> The PointID of the PI Point. In this case the objectID won't be used.
  • ServerID (Guid) --> The ID of the AF Server or the PI Data Archive.
  • Version (int) --> The version of the Web ID. If it is using the Web ID 2.0, the version should be 1.
  • WebIdType (WebIdType) --> The WebIDType which could be Full, PathOnly, IDOnly, LocalDOnly, DefaultIDOnly.


If you take a look at the Specification Tables, you would realize that some objects have ObjectID, some objects don't. Some have OwnerID, some don't. PIDataServer and PIAssetServer don't have OwnerID and ObjectID but only the ServerID. After taking a look at all object types, we have realized that there are 5 categories of WebIDs according to the table below:


CategoryServer IDObject IDOwner IDOwner MarkerExamples
AXPIDataArchive and PI AssetServer

PIAnalysis, PIAnalysisCategory, PIAnalysisTemplate, PIAnalysisRulePlugIn, PIAttributeCategory, PIEventFrame, PITimeRulePlugIn, PISecurityIdentity,

PISecurityMapping, PITable, PITableCategory, PIUnit, PIUnitClass, PIAssetDatabase, PIElement, PIElementCategory, PIElementTemplate

DXXXXPIAnalysisRule, PIAttribute, PIAttributeTemplate, PIEnumerationValue, PITimeRule
EXX (PointID instead of ObjectID)PIPoint


NOTE: Since the client library doesn't have Notification objects, they were removed from the table above.


In all types of objects, the 4 first characters are processed the same way:

- First character: Web ID type

- Second character: Web ID version

- Third and fourth characters: Marker of the object type


The other characters are processed by the ProcessGuidAndPaths() method below:


        private void ProcessGuidsAndPaths(string webId, bool hasMarkerOwner, WebIdStringType webIdStringType, bool usePIPoint = false)
            string restWebId = webId.Substring(4);

            if (hasMarkerOwner == true)
                string markerOwner = restWebId.Substring(0, 1);
                restWebId = restWebId.Substring(1);

            if ((WebIdType == WebIdType.Full) || (WebIdType == WebIdType.IDOnly))
                string encodedServerID = restWebId.Substring(0, 22);
                ServerID = DecodeGUID(encodedServerID);
                restWebId = restWebId.Substring(22);

                if (webIdStringType == WebIdStringType.ThreeGuids)
                    string encodedOwnerID = restWebId.Substring(0, 22);
                    OwnerID = DecodeGUID(encodedOwnerID);
                    restWebId = restWebId.Substring(22);
                if ((webIdStringType == WebIdStringType.ThreeGuids) ||
                    (webIdStringType == WebIdStringType.TwoGuids))

                    if (usePIPoint == false)
                        string encodedObjectID = restWebId.Substring(0, 22);
                        ObjectID = DecodeGUID(encodedObjectID);
                        restWebId = restWebId.Substring(22);
                        string encodedObjectID = restWebId.Substring(0, 6);
                        PointID = DecodeInt(encodedObjectID);
                        restWebId = restWebId.Substring(6);


            if ((WebIdType == WebIdType.Full) || (WebIdType == WebIdType.PathOnly))
                string encodedPath = restWebId;
                Path = DecodeString(encodedPath);


Each type of object belongs to a different category. Each category calls the ProcessGuidsAndPaths() function according to the table below:


CategoryHow to call ProcessGuidsAndPaths
AProcessGuidsAndPaths(webId, false, WebIdStringType.OneGuid, false)
BProcessGuidsAndPaths(webId, false, WebIdStringType.TwoGuids, false)
CProcessGuidsAndPaths(webId, true, WebIdStringType.ThreeGuids, false)
DProcessGuidsAndPaths(webId, true, WebIdStringType.TwoGuids, false)
EProcessGuidsAndPaths(webId, false, WebIdStringType.TwoGuids, true)


We have executed a Unit test to make sure that no exception is thrown in this process and that the results are correct:


            string webIdType = "Full";
            PIAnalysis piAnalysis = client.Analysis.GetByPath(Constants.AF_ANALYSIS_PATH, null, webIdType);
            WebIdInfo piAnalysisWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAnalysis.WebId);
            Assert.AreEqual(Constants.AF_ANALYSIS_PATH.ToUpper().Substring(2), piAnalysisWebIdInfo.Path);
            Assert.AreEqual(piAnalysis.Id, piAnalysisWebIdInfo.ObjectID.ToString());

            PIAnalysisCategory piAnalysisCategory = client.AnalysisCategory.GetByPath(Constants.AF_ANALYSIS_CATEGORY_PATH, null, webIdType);
            WebIdInfo piAnalysisCategoryWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAnalysisCategory.WebId);
            Assert.AreEqual(Constants.AF_ANALYSIS_CATEGORY_PATH.ToUpper().Substring(2), piAnalysisCategoryWebIdInfo.Path);
            Assert.AreEqual(piAnalysisCategory.Id, piAnalysisCategoryWebIdInfo.ObjectID.ToString());

            PIAnalysisRule piAnalysisRule = client.AnalysisRule.GetByPath(Constants.AF_ANALYSIS_RULE_PATH, null, webIdType);
            WebIdInfo piAnalysisRuleWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAnalysisRule.WebId);
            Assert.AreEqual(Constants.AF_ANALYSIS_RULE_PATH.ToUpper().Substring(2), piAnalysisRuleWebIdInfo.Path);
            Assert.AreEqual(piAnalysisRule.Id, piAnalysisRuleWebIdInfo.ObjectID.ToString());

            PIAnalysisRulePlugIn piAnalysisRulePlugIn = client.AnalysisRulePlugIn.GetByPath(Constants.AF_ANALYSIS_RULE_PLUGIN_PATH, null, webIdType);
            WebIdInfo piAnalysisRulePlugInWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAnalysisRulePlugIn.WebId);
            Assert.AreEqual(Constants.AF_ANALYSIS_RULE_PLUGIN_PATH.ToUpper().Substring(2), piAnalysisRulePlugInWebIdInfo.Path);
            Assert.AreEqual(piAnalysisRulePlugIn.Id, piAnalysisRulePlugInWebIdInfo.ObjectID.ToString());

            PIAnalysisTemplate piAnalysisTemplate = client.AnalysisTemplate.GetByPath(Constants.AF_ANALYSIS_TEMPLATE_PATH, null, webIdType);
            WebIdInfo piAnalysisTemplateWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAnalysisTemplate.WebId);
            Assert.AreEqual(Constants.AF_ANALYSIS_TEMPLATE_PATH.ToUpper().Substring(2), piAnalysisTemplateWebIdInfo.Path);
            Assert.AreEqual(piAnalysisTemplate.Id, piAnalysisTemplateWebIdInfo.ObjectID.ToString());

            PIAssetDatabase piAssetDatabase = client.AssetDatabase.GetByPath(Constants.AF_DATABASE_PATH, null, webIdType);
            WebIdInfo piAssetDatabaseWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAssetDatabase.WebId);
            Assert.AreEqual(Constants.AF_DATABASE_PATH.ToUpper().Substring(2), piAssetDatabaseWebIdInfo.Path);
            Assert.AreEqual(piAssetDatabase.Id, piAssetDatabaseWebIdInfo.ObjectID.ToString());

            PIAssetServer piAssetServer = client.AssetServer.GetByPath(Constants.AF_SERVER_PATH, null, webIdType);
            WebIdInfo piAssetServerWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAssetServer.WebId);
            Assert.AreEqual(Constants.AF_SERVER_PATH.ToUpper().Substring(2), piAssetServerWebIdInfo.Path);
            Assert.AreEqual(piAssetServer.Id, piAssetServerWebIdInfo.ServerID.ToString());

            PIAttribute piAttribute = client.Attribute.GetByPath(Constants.AF_ATTRIBUTE_PATH, null, webIdType);
            WebIdInfo piAttributeWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAttribute.WebId);
            Assert.AreEqual(Constants.AF_ATTRIBUTE_PATH.ToUpper().Substring(2), piAttributeWebIdInfo.Path);
            Assert.AreEqual(piAttribute.Id, piAttributeWebIdInfo.ObjectID.ToString());

            PIAttributeCategory piAttributeCategory = client.AttributeCategory.GetByPath(Constants.AF_ATTRIBUTE_CATEGORY_PATH, null, webIdType);
            WebIdInfo piAttributeCategoryWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAttributeCategory.WebId);
            Assert.AreEqual(Constants.AF_ATTRIBUTE_CATEGORY_PATH.ToUpper().Substring(2), piAttributeCategoryWebIdInfo.Path);
            Assert.AreEqual(piAttributeCategory.Id, piAttributeCategoryWebIdInfo.ObjectID.ToString());

            PIAttributeTemplate piAttributeTemplate = client.AttributeTemplate.GetByPath(Constants.AF_ATTRIBUTE_TEMPLATE_PATH, null, webIdType);
            WebIdInfo piAttributeTemplateWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAttributeTemplate.WebId);
            Assert.AreEqual(Constants.AF_ATTRIBUTE_TEMPLATE_PATH.ToUpper().Substring(2), piAttributeTemplateWebIdInfo.Path);
            Assert.AreEqual(piAttributeTemplate.Id, piAttributeTemplateWebIdInfo.ObjectID.ToString());

            PIDataServer piDataServer = client.DataServer.GetByPath(Constants.PI_DATA_SERVER_PATH, null, webIdType);
            WebIdInfo piDataServerWebIdInfo = client.WebIdHelper.GetWebIdInfo(piDataServer.WebId);
            Assert.AreEqual(Constants.PI_DATA_SERVER_PATH.ToUpper().Substring(2), piDataServerWebIdInfo.Path);
            Assert.AreEqual(piDataServer.Id, piDataServerWebIdInfo.ServerID.ToString());

            PIElement piElement = client.Element.GetByPath(Constants.AF_ELEMENT_PATH, null, webIdType);
            WebIdInfo piElementWebIdInfo = client.WebIdHelper.GetWebIdInfo(piElement.WebId);
            Assert.AreEqual(Constants.AF_ELEMENT_PATH.ToUpper().Substring(2), piElementWebIdInfo.Path);
            Assert.AreEqual(piElement.Id, piElementWebIdInfo.ObjectID.ToString());

            PIElementCategory piElementCategory = client.ElementCategory.GetByPath(Constants.AF_ELEMENT_CATEGORY_PATH, null, webIdType);
            WebIdInfo piElementCategoryWebIdInfo = client.WebIdHelper.GetWebIdInfo(piElementCategory.WebId);
            Assert.AreEqual(Constants.AF_ELEMENT_CATEGORY_PATH.ToUpper().Substring(2), piElementCategoryWebIdInfo.Path);
            Assert.AreEqual(piElementCategory.Id, piElementCategoryWebIdInfo.ObjectID.ToString());

            PIElementTemplate piElementTemplate = client.ElementTemplate.GetByPath(Constants.AF_ELEMENT_TEMPLATE_PATH, null, webIdType);
            WebIdInfo piElementTemplateWebIdInfo = client.WebIdHelper.GetWebIdInfo(piElementTemplate.WebId);
            Assert.AreEqual(Constants.AF_ELEMENT_TEMPLATE_PATH.ToUpper().Substring(2), piElementTemplateWebIdInfo.Path);
            Assert.AreEqual(piElementTemplate.Id, piElementTemplateWebIdInfo.ObjectID.ToString());

            PIEnumerationSet piEnumerationSet = client.EnumerationSet.GetByPath(Constants.AF_ENUMERATION_SET_PATH, null, webIdType);
            WebIdInfo piEnumerationSetWebIdInfo = client.WebIdHelper.GetWebIdInfo(piEnumerationSet.WebId);
            Assert.AreEqual(Constants.AF_ENUMERATION_SET_PATH.ToUpper().Substring(2), piEnumerationSetWebIdInfo.Path);
            Assert.AreEqual(piEnumerationSet.Id, piEnumerationSetWebIdInfo.ObjectID.ToString());

            PIEnumerationValue piEnumerationValue = client.EnumerationValue.GetByPath(Constants.AF_ENUMERATION_VALUE_PATH, null, webIdType);
            WebIdInfo piEnumerationValueWebIdInfo = client.WebIdHelper.GetWebIdInfo(piEnumerationValue.WebId);
            Assert.AreEqual(Constants.AF_ENUMERATION_VALUE_PATH.ToUpper().Substring(2), piEnumerationValueWebIdInfo.Path);
            Assert.AreEqual(piEnumerationValue.Id, piEnumerationValueWebIdInfo.ObjectID.ToString());

            PIEventFrame piEventFrame = client.EventFrame.GetByPath(Constants.AF_EVENT_FRAME_PATH, null, webIdType);
            WebIdInfo piEventFrameWebIdInfo = client.WebIdHelper.GetWebIdInfo(piEventFrame.WebId);
            Assert.AreEqual(Constants.AF_EVENT_FRAME_PATH.ToUpper().Substring(2), piEventFrameWebIdInfo.Path);
            Assert.AreEqual(piEventFrame.Id, piEventFrameWebIdInfo.ObjectID.ToString());

            PIPoint piPoint = client.Point.GetByPath(Constants.PI_POINT_PATH, null, webIdType);
            WebIdInfo piPointWebIdInfo = client.WebIdHelper.GetWebIdInfo(piPoint.WebId);
            Assert.AreEqual(Constants.PI_POINT_PATH.ToUpper().Substring(2), piPointWebIdInfo.Path);
            Assert.AreEqual(piPoint.Id.ToString(), piPointWebIdInfo.PointID.ToString());

            PISecurityIdentity piSecurityIdentity = client.SecurityIdentity.GetByPath(Constants.AF_SECURITY_IDENTITY_PATH, null, webIdType);
            WebIdInfo piSecurityIdentityWebIdInfo = client.WebIdHelper.GetWebIdInfo(piSecurityIdentity.WebId);
            Assert.AreEqual(Constants.AF_SECURITY_IDENTITY_PATH.ToUpper().Substring(2), piSecurityIdentityWebIdInfo.Path);
            Assert.AreEqual(piSecurityIdentity.Id, piSecurityIdentityWebIdInfo.ObjectID.ToString());

            PISecurityMapping piSecurityMapping = client.SecurityMapping.GetByPath(Constants.AF_SECURITY_MAPPING_PATH, null, webIdType);
            WebIdInfo piSecurityMappingWebIdInfo = client.WebIdHelper.GetWebIdInfo(piSecurityMapping.WebId);
            Assert.AreEqual(Constants.AF_SECURITY_MAPPING_PATH.ToUpper().Substring(2), piSecurityMappingWebIdInfo.Path);
            Assert.AreEqual(piSecurityMapping.Id, piSecurityMappingWebIdInfo.ObjectID.ToString());

            PITable piTable = client.Table.GetByPath(Constants.AF_TABLE_PATH, null, webIdType);
            WebIdInfo piTableWebIdInfo = client.WebIdHelper.GetWebIdInfo(piTable.WebId);
            Assert.AreEqual(Constants.AF_TABLE_PATH.ToUpper().Substring(2), piTableWebIdInfo.Path);
            Assert.AreEqual(piTable.Id, piTableWebIdInfo.ObjectID.ToString());

            PITableCategory piTableCategory = client.TableCategory.GetByPath(Constants.AF_TABLE_CATEGORY_PATH, null, webIdType);
            WebIdInfo piTableCategoryWebIdInfo = client.WebIdHelper.GetWebIdInfo(piTableCategory.WebId);
            Assert.AreEqual(Constants.AF_TABLE_CATEGORY_PATH.ToUpper().Substring(2), piTableCategoryWebIdInfo.Path);
            Assert.AreEqual(piTableCategory.Id, piTableCategoryWebIdInfo.ObjectID.ToString());

            PIUnit piUnit = client.Unit.GetByPath(Constants.AF_UOM_PATH, null, webIdType);
            WebIdInfo piUnitWebIdInfo = client.WebIdHelper.GetWebIdInfo(piUnit.WebId);
            Assert.AreEqual(Constants.AF_UOM_PATH.ToUpper().Substring(2), piUnitWebIdInfo.Path);
            Assert.AreEqual(piUnit.Id, piUnitWebIdInfo.ObjectID.ToString());

            PIUnitClass piUnitClass = client.UnitClass.GetByPath(Constants.AF_UOM_CLASS_PATH, null, webIdType);
            WebIdInfo piUnitClassWebIdInfo = client.WebIdHelper.GetWebIdInfo(piUnitClass.WebId);
            Assert.AreEqual(Constants.AF_UOM_CLASS_PATH.ToUpper().Substring(2), piUnitClassWebIdInfo.Path);
            Assert.AreEqual(piUnitClass.Id, piUnitClassWebIdInfo.ObjectID.ToString());



Generating a Web ID given a path


Generating a Web ID on the client is a very simple task. Just call the client.WebIdHelper.GenerateWebIdByPath() method. Here are some examples:


            string webId = client.WebIdHelper.GenerateWebIdByPath(Constants.AF_DATABASE_PATH, typeof(PIAssetDatabase));
            string webId2 = client.WebIdHelper.GenerateWebIdByPath(Constants.AF_ATTRIBUTE_PATH, typeof(PIAttribute), typeof(PIElement));


The inputs of this function are:

  • The path of the object (required)
  • The type of the object (required)
  • The type of the owner of the object (optional)


The third input should only be used for objects that belong to the Category C and D. There is a method on the client library that validates this parameter which was developed according to the table specification:


      private void ValidateTypeAndOwnerType(Type type, Type ownerType)
            if (type == typeof(PIAttribute))
                if ((ownerType != typeof(PIElement)) && (ownerType != typeof(PIEventFrame)))
                    throw new WebIdException("PIAttribte owner type must be a PIElement or a PIEventFrame.");
            else if (type == typeof(PIAttributeTemplate))
                if ((ownerType != typeof(PIElementTemplate)))
                    throw new WebIdException("PIElementTemplate owner type must be a PIElementTemplate.");
            else if ((type == typeof(PIEnumerationSet)) && (type == typeof(PIEnumerationValue)))
                if ((ownerType != typeof(PIDataServer)) && (ownerType != typeof(PIAssetServer)))
                    throw new WebIdException("PIEnumerationSet and  PIEnumerationValue owner type must be a PIDataServer or PIAssetServer.");
            else if (type == typeof(PITimeRule))
                if ((ownerType != typeof(PIAnalysis)) && (ownerType != typeof(PIAnalysisTemplate)))
                    throw new WebIdException("PITimeRule owner type must be a PIAnalysis and PIAnalysisTemplate.");



The returned WebID is generated as follows:


  • First two charcaters: "P1" (PathOnly WebIDType and Version 1)
  • Two characters for the marker's object
  • One character for the marker's owner object (optional)
  • Encoded path (after deleting the first two backslashes)


     public string GenerateWebIdByPath(string path, Type type, Type ownerType = null)
            ValidateTypeAndOwnerType(type, ownerType);
            string marker = GetMarker(type);
            string ownerMarker = GetOwnerMarker(ownerType);

            if (path.Substring(0, 2) == "\\\\")
                path = path.Substring(2);
            string encodedPath = Encode(path.ToUpperInvariant());
            return string.Format("P1{0}{1}{2}", marker, ownerMarker, encodedPath);




I hope that this blog post helped you:

  • Generate the Web ID using this client library faster
  • Understand the main concepts of Web ID 2.0
  • Write code in other platforms to generate the Web ID client side.


If you have questions, please post them below!

We have news about the PI Web API client libraries for .NET!


We have migrated our PI Web API client library for .NET Core to .NET Standard, since it is a set of APIs that all .NET platforms have to implement. As a result, this library is compatible with .NET Framework and .NET Core. The link of the new repository is here. The PI Web API client library for .NET Framework should only be used if you want to take advantage of PI Web API Channels which is not available on the .NET Standard version. Please read this blog post about this feature.


It has never been easier to install the PI Web API client library in your .NET projects. Now it is available on NuGet!!




You can find more information about this package on NuGet.


It is also very easy to install. Just open Package Manager and type:


Install-Package OSIsoft.PIDevClub.PIWebApiClient -Version 1.1.6


Please refer to this sample console app in order to get started faster!


If you have questions, please post it in the comments section.



Channels  is the PI Web API version of the AFDataPIpe feature of the AF SDK. It is a way to receive continuous updates about a stream or stream set. Rather than using a typical HTTP request, channels are accessed using the Web Socket protocol. There are numerous Web Socket client libraries in several languages available for use with channels.


The latest release of PI Web API client library for .NET Framework adds 3 methods to the PIWebApiClient.Channels class in order to use this feature client side using the IObserver pattern. This pattern was chosen due to the fact that PI Developers are used to using this pattern with AFDataPipe.


Methods added to the ChannelsApi class


PI Web API Channels can be used with 3 different URLs:

  • wss://myserver/piwebapi/streams/{webId}/channel (webId must be a stream
  • wss://myserver/piwebapi/streamsets/{webId}/channel (webId should be a streamset)
  • wss://myserver/piwebapi/streamsets/channel?webId={webId} (multiple streams)


A stream in PI Web API could be a PI Point or an AF Attribute with a data reference. A stream set is defined as a collection of streams. Streams inside a stream set can be independent, or share the same base element (e.g. element or event frame) or parent (e.g. parent attribute). Please review the PI Web API help for more information.


As a result, the following 3 methods were added to the ChannelsApi:



All of those methods returns a task still in execution. The reason it was designed this way is to avoid blocking the main thread of your application. The inputs of those methods are:


  • The webId of a stream or a stream,  or the list of webIds from multiple streams
  • A custom class that implements IObserver<PIItemsStreamValues> pattern. This is how you customize how the application would process new received values.
  • CancellationTokenSource which is used to cancel your task.




Let's start by creating our custom class which implements the IObserver<PIItemsStreamValues> interface.


    public class CustomChannelObserver : IObserver<PIItemsStreamValues>
        public void OnCompleted()

        public void OnError(Exception error)

        public void OnNext(PIItemsStreamValues value)
            foreach(PIStreamValues item in value.Items)
                foreach (PITimedValue subItem in item.Items)
                    Console.WriteLine("\n\nName={0}, Path={1}, WebId={2}, Value={3}, Timestamp={4}", item.Name, item.Path, item.WebId, subItem.Value, subItem.Timestamp);



Since this is just an example for you to refer to, this custom class will show the received values on the console. I suggest taking a look at the PIItemsStreamValues class. Its Items property has many PIStreamValues. The PIStreamValues has also the Items property which has many PITimedValue objects.


The code snippet of the main thread which will call the StartStreamSets method is below:


        private static void ChannelsExamples()
            PIWebApiClient client = new PIWebApiClient("", true);
            PIPoint point1 = client.Point.GetByPath("\\\\marc-pi2016\\sinusoid");
            PIPoint point2 = client.Point.GetByPath("\\\\marc-pi2016\\sinusoidu");
            PIPoint point3 = client.Point.GetByPath("\\\\marc-pi2016\\cdt158");
            List<string> webIds = new List<string>() { point1.WebId, point2.WebId, point3.WebId };

            CancellationTokenSource cancellationSource = new CancellationTokenSource();
            IObserver<PIItemsStreamValues> observer = new CustomChannelObserver();
            Task channelTask = client.Channel.StartStreamSets(webIds, observer, cancellationSource.Token);


This is how the code works:


  • Create a new instance of PI Web API using Kerberos authentication
  • Get the 3 WebIDs from the SINUSOID, SINUSOIDU and CDT158 points.
  • Create a list of those 3 WebIDs.
  • Instantiate a cancellationTokenSource and the custom class with the IObserver pattern.
  • Call StartStreamSets method which will return a task.
  • Wait 2 minutes using the Thread.Sleep() method. During this period all the events will be received through the OnNext() method from the custom class with the IObserver pattern.
  • Call cancellationTokenSource.Cancel() method.
  • Wait for the returned task to finish.


The new values are shown on the console:



Please refer to this sample application for more details of the implementation.




For all PI developers who are used to using IObserver pattern with AFDataPIpe, I think they will find pretty easy to use it with PI Web API Channels. Unfortunately, this feature is only available on the PI Web API client library for .NET Framework and not on the .NET Standard version due to the fact that WebSockets from .NET Core is not compatible with PI Web API Channels security.

When: March 19, 2018 - April 16, 2018

Where: Anywhere

Join us at our second Visualization Virtual Hackathon to learn, network, and compete for prizes. We will provide all the tools, access to subject matter experts, and a ready-to-start environment. Just show up with your ideas and energy to develop some amazing visualizations in this 4 week event!

The hackathon will focus around adding new and innovative extensions to the OSIsoft visualization platform, PI Vision. You are free to use any of your own data or use one of our provided environments that will include several Asset Based Example Kits. At the end, your application will be judged by a group of experts. The top applications will be awarded prizes, and you and your team will be publicly recognized for the achievement.

For questions and discussions about the event, please check out our Hackathons space in PI DevClub. This will be the best medium to have your questions answered. In case you prefer to discuss something privately about this event, please contact

Q: Who is this hackathon for?

A: If you are a PI System developer, architect, integrator, idea person, subject matter expert, data scientist, or a technologist interested in data visualization, this event is for you.

Q: How do I sign up? What is the flow of the event?

A: Once you decide to join the hackathon, we ask you to sign up you and your teammates (up to 4 people per team) on this sheet. You can then start the work on your project while engaging with us and the rest of the hackers in this space. We highly encourage you to continuously follow this space because we will be posting updates and news about the event there. You are expected to submit your entry and other requirements by the end date of the event through GitHub. We will judge the entries and announce the winners at PI World 2018 in San Francisco.

Q: What do I need to bring?

A: For this hackathon, OSIsoft will be hosting a sneak peek version of PI Vision 4.x on Microsoft Azure for all contestants to use. For development, you will be able to use your own development environment.

Q: How do I submit my entry?

A: All submissions will be done through PI Square. In order to submit your project you will need to create a blog post; you can save it as a draft and make it public at the end of the event. Your post should include:

  • A description of the general purpose of your project, including who and why someone would use it
  • A picture of your project in use
  • A video that demonstrates your project, is a nice screen capturing tool
  • A link to your GitHub repository where your source code is located. Please note that all source code must be published under the Apache 2.0 license.


Q: Is GitHub required?

A: Yes, to be accepted all contributions must be hosted on GitHub. We are making this a prerequisite to facilitate collaboration within the community. You or one of your team members will need a GitHub account, and you will need to create a new repository in your personal GitHub account to host your work. For more information about using GitHub, you can look at their contributing guidelines,


Q: How will the judging take place? A: Judging for the event will  be based on your video submission as well as the following criteria:

  • Creativity and Originality
  • Potential Business Impact
  • Technical Implementation
  • Data Analysis and Insight
  • UI/UX


We will also have a bonus category for best Data Science application at this year's hackathon, complete with its own prize.


Q: What will I win?

A: Each member of the winning teams will receive


First Place: Nest Thermostat, Google Home, 3 Google Home Minis, and a Chromecast Ultra.

Second Place: Google Home, 3 Google Home Minis, and a Chromecast Ultra.

Third Place: Google Home, 1 Google Home Mini, and a Chromecast Ultra.


Best Data Science App: Google Home

Q: Does this replace the traditional hackathon held at the UC?

A: No, we will still be holding our regular hackathon at the UC this year.


Q: How can I learn more?

A: Check out the recording of our webinar for more details about the Visualization Virtual Hackathon 2018. There will also be a sneak peek into PI Vision 4 extensibility on March 9th.

OSIsoft is excited to present a brand new utility called the AF Transfomer.


The AF Transformer can take one or more source AF structures, reshape those structures and then write them to another AF destination.  This can be useful when you have one or more AF reference structures containing enough meta data to be able to shape into other AF structures that benefit other users and use cases in an organization.  We have found many organizations desire different AF structures for different purposes.


Example transformation where a new parent element is created from an attribute:

DevClub blog no.1.png


One use case that comes up regularly is for transforming the AF structures built by PI Connectors.  The source AF structures built by PI Connectors can serve as a useful model for the PI Admin and controls engineer to confirm that the right data is being collected because the AF structure looks exactly like the data source's meta data.  However, the shape of data coming from data sources often does not fit perfectly the way an organization or end user desire to consume the data.  Hence, the AF Transformer can help.


You may have seen this at the London UC where we showed a few examples during this talk.  Forward to about 14 minutes in and the next 10 minutes cover the basics of what problem this tool helps you solve and then gives a few demos.  Feel free to watch when you have a few minutes. Additionally, we will follow this blog with some more detailed examples for you to learn from.  There are plenty of examples (including source AF databases to transform) installed with the utility.


We'd like the community of developers, partners, and customers alike to share their questions, their successes and maybe a few examples of what they create in the Developers Club community.  Stay tuned for more content from us and in the meanwhile, we look forward to your questions, comments, suggestions, and successes!


Download the installation kit, user manual, reference guide and release notes at the AF Transformer group site on PI Square.  The group is a private group but we are glad to give you access!  Just click this link, request access, and we will approve shortly and notify you when you have access.


And here's a very easy hands-on tutorial!

A Sneak Peek into the PI Vision 4 Extensibility Model


March 9, 2018

9:00 AM (PT)/12:00 PM (ET)/6:00 PM (ES)


Register now!


In this webinar you will be introduced to the new extensibility model coming in PI Vision 4 and we will walk through the creation of a custom symbol using OSIsoft's new developer seed project for PI Vision 4. There will be an opportunity to ask questions at the end. This webinar is initially intended to be a companion to the Visualization Virtual Hackathon 2018 webinar, but will give all viewers a sneak peek at PI Vision 4 and the new extensibility model.


We will cover:


  • PI Vision 4
  • PI Vision 4 developer seed project
  • Creating a new symbol for PI Vision 4
  • Q&A


The audience will have a chance to ask questions directly to the organizers of the event.



  • John Sintilas, Principal Software Developer, OSIsoft
  • Marcos Vainer Loeff, Sr. Technology Enablement Engineer, OSIsoft


We'd love to hear your comments and questions here in the comments or in the PI Developers Club forum!

Visualization Virtual Hackathon 2018


February 15, 2018

9:00 AM (PT)/12:00 PM (ET)/6:00 PM (ES)


Register now!


Learn, innovate, compete, and win big at OSIsoft's second Visualization Virtual Hackathon! If you are a PI System developer, architect, designer, or data professional this event is for you. In this webinar we will be announcing our second Visualization Virtual Hackathon. We will explain the flow of the event:


  • Agenda
  • Goals of the Hackathon
  • Registration
  • Rules
  • Resources and Documentation available
  • Judging criteria
  • Size of teams
  • Project submission
  • Prizes
  • Winners announcement


The audience will have a chance to ask questions directly to the organizers of the event.



  • John Sintilas, Principal Software Developer, OSIsoft
  • Marcos Vainer Loeff, Sr. Technology Enablement Engineer, OSIsoft


We'd love to hear your comments and questions here in the comments or in the PI Developers Club forum!

Filter Blog

By date: By tag: