7 Replies Latest reply on Apr 22, 2018 4:15 PM by SyedHussain Branched to a new discussion.

    AF SDK fetch digital tag value

    SyedHussain

      AF SDK fetch digital tag value

       

      Hi Rick

       

      I thank you very much. My objective is to get all recorded values of digital tag for a day and total the number of hours the valve was OPEN. I am using AFSDK and could not get any function to fetch the digital tags name from its associated digital set. I checked PIStateSets and tried to use

      Dim setValue As OSIsoft.AF.PI.PIStateSets = TryCast(myvalue.Value, PI.PIStateSets)

      and get setvalue.name but its not avilable

       

      Appreciate for any help please.

       

      Thanks

        • Re: AF SDK fetch digital tag value
          Rick Davin

          Hi Syed,

           

          Please DO NOT create a new thread for each new post related to a thread.  Readers get lost quickly as the conversation loses all previous context.

           

          You have what many describe as an XY Problem.  That is you want to solve for X but somewhere along the lines are absolutely determined that the one and only way to solve for X is to first solve for Y.  To fetch recorded values, and to find the duration related to a given state name, does NOT require fetching the PIStateSets!  So I will ignore the Y (finding PIStateSets) and instead focus on the X (get recorded values for a digital tag and find the duration for a given state by name).

           

          I tried looking at PIPoint.FilteredSummaries and AFCalculation.CalculateSummaries but since this is a digital tag, it's not numeric and either will throw an exception or return a value that I can't do anything with.  Instead, I choose to list all recorded values for a given time span, being careful to include Interpolated values on the start and end time boundaries, and then I can loop over those values in memory to calculate durations.  I do all of this without ever fetching the PIStateSet.

           

          Imports OSIsoft.AF
          Imports OSIsoft.AF.Time
          Imports OSIsoft.AF.Asset
          Imports OSIsoft.AF.PI
          Imports OSIsoft.AF.Data
          
          Module Syed
          
              Public Sub Example()
          
                  Const yourDigitalTagName As String = "CDM158"
                  Const yourDesiredStateName As String = "Cascade"
          
                  Dim dataArchive As PIServer = New PIServers().DefaultPIServer
                  Dim tag As PIPoint = PIPoint.FindPIPoint(dataArchive, yourDigitalTagName)
          
                  Dim startTime As AFTime = New AFTime("y")
                  Dim endTime As AFTime = New AFTime("t")
                  Dim timeRange As AFTimeRange = New AFTimeRange(startTime, endTime)
          
                  ' You most likely want to use Interpolated boundary
                  Dim recordedValues As AFValues = tag.RecordedValues(timeRange, AFBoundaryType.Interpolated, Nothing, False)
          
                  ' We already have the recorded values in memory so we can avoid an expensive trip to the server
                  Dim durations As IDictionary(Of AFEnumerationValue, TimeSpan) = GetDigitalDurations(recordedValues)
          
                  Console.WriteLine("Durations Per State:")
                  For Each item As KeyValuePair(Of AFEnumerationValue, TimeSpan) In durations
                      Console.WriteLine($"  Key: {item.Key}, Value: {item.Value}")
                  Next
                  Console.WriteLine()
                  Console.WriteLine("Desired State Name:")
                  Console.WriteLine($"  {yourDesiredStateName}")
          
                  Console.WriteLine()
                  Console.WriteLine("Duration of Desired State:")
                  Console.WriteLine($"  {GetDuration(durations, yourDesiredStateName)}")
          
                  Dim stateSet As AFEnumerationSet = GetStateSet(tag)
                  Console.WriteLine()
                  Console.WriteLine("State Set for Tag:")
                  Console.WriteLine($"   {stateSet}")
                  Console.WriteLine($"Number of states: {stateSet.Count}")
                  For Each state As AFEnumerationValue In stateSet
                      Console.WriteLine($"   State: {state.Name}, Code: {state.Value}")
                  Next
          
                  Console.WriteLine()
          
              End Sub
          
              Public Function GetDigitalDurations(values As AFValues) As IDictionary(Of AFEnumerationValue, TimeSpan)
          
                  If values Is Nothing OrElse values.Count = 0 OrElse values.PIPoint Is Nothing OrElse values.PIPoint.PointType <> PIPointType.Digital Then
                      Throw New ArgumentException("Invalid argument. Values must be backed by a PIPoint that is a Digital tag.")
                  End If
          
                  Dim dict As Dictionary(Of AFEnumerationValue, TimeSpan) = New Dictionary(Of AFEnumerationValue, TimeSpan)
          
                  Dim bogusState As AFEnumerationValue = New AFEnumerationValue("totally bogus state", -24680)
                  Dim oldState As AFEnumerationValue = bogusState
                  Dim oldTime As AFTime = values(0).Timestamp
          
                  For Each value As AFValue In values
                      Dim state As AFEnumerationValue = DirectCast(value.Value, AFEnumerationValue)
                      ' Make sure current state has a dict entry
                      If Not dict.ContainsKey(state) Then
                          dict.Add(state, TimeSpan.Zero)
                      End If
                      ' First time through, immediately iterate to next value to avoid counting totally bogus state
                      If oldState = bogusState Then
                          oldState = state
                          oldTime = value.Timestamp
                          Continue For
                      End If
                      ' We have enough info to log the span of the oldState
                      Dim oldSpan As TimeSpan = dict(oldState)
                      dict(oldState) = oldSpan + (value.Timestamp - oldTime)
                      ' Adjust old stuff before looping
                      oldState = state
                      oldTime = value.Timestamp
                  Next
          
                  Return dict
          
              End Function
          
              ' Case insensitive match on state name
              Public Function GetDuration(dict As IDictionary(Of AFEnumerationValue, TimeSpan), stateName As String) As TimeSpan
                  For Each item As KeyValuePair(Of AFEnumerationValue, TimeSpan) In dict
                      If String.Compare(item.Key.Name, stateName, True) = 0 Then
                          Return item.Value
                      End If
                  Next
                  Return TimeSpan.Zero
              End Function
          
              ' This assumes you already have a full AFEnumerationValue, but that must match more than just Name and Value.
              ' It must match on every property of the AFEnumerationValue.
              Public Function GetDuration(dict As IDictionary(Of AFEnumerationValue, TimeSpan), state As AFEnumerationValue) As TimeSpan
                  If dict.ContainsKey(state) Then
                      Return dict(state)
                  End If
                  Return TimeSpan.Zero
              End Function
          
              ' Just for giggles, here is how to get the "state set" for a digital tag.
              Public Function GetStateSet(tag As PIPoint) As AFEnumerationSet
                  If tag Is Nothing OrElse tag.PointType <> PIPointType.Digital Then
                      Throw New ArgumentException("Tag cannot be Nothing and must be a Digital tag.")
                  End If
          
                  If Not tag.IsAttributeLoaded(PICommonPointAttributes.DigitalSetName) Then
                      tag.LoadAttributes(PICommonPointAttributes.DigitalSetName)
                  End If
          
                  Dim stateSetName As String = tag.GetAttributes(PICommonPointAttributes.DigitalSetName)(PICommonPointAttributes.DigitalSetName).ToString()
                  Dim stateSet As AFEnumerationSet = tag.Server.StateSets(stateSetName)
          
                  Return stateSet
              End Function
          
          End Module
          

           

           

          Here is the sample output:

           

          Durations Per State:

             Key: Program, Value: 09:21:45

             Key: Prog-Auto, Value: 01:30:01

             Key: Cascade, Value: 06:50:22

             Key: Auto, Value: 03:14:17

             Key: Manual, Value: 01:12:30

             Key: Shutdown, Value: 01:51:05

           

          Desired State Name:

             Cascade

           

          Duration of Desired State:

             06:50:22

           

          State Set for Tag:

             Modes

          Number of states: 5

             State: Manual, Code: 0

             State: Auto, Code: 1

             State: Cascade, Code: 2

             State: Program, Code: 3

             State: Prog-Auto, Code: 4

          1 of 1 people found this helpful
          • Re: AF SDK fetch digital tag value
            tramachandran

            Rick Davin has great code sample that you can use.

            What is your end goal?

             

            If it is just to get the total the number of hours the valve was OPEN, then PE/Analytics can easily accomplish this using TimeEq function TimeEq('digitaltag', '1-Apr-2018', '*', "OPEN")

            PIPoint.RecordedValues also as the option to apply a filter expression that follows the performance equation syntax 'digitaltag' = "OPEN".

             

              • Re: AF SDK fetch digital tag value
                Rick Davin

                Hi Thyag,

                 

                I do the calculations myself because he said he wanted to fetch the recorded values first.  Once the values are in memory, I try to avoid making another trip to the server.  Let's pretend he doesn't want recorded values after all, but rather just wants the equivalent of the TimeEq function but he also insists on doing it with AF SDK.  This works:

                 

                        Dim expr As String = $"TimeEq('{tag.Name}', 'y', 't', ""{yourDesiredStateName}"")"
                
                        Console.WriteLine($"Expr = {expr}")
                        Dim calc = AFCalculation.CalculateAtIntervals(dataArchive, expr, timeRange, New AFTimeSpan(timeRange.Span))
                        Dim endValue As AFValue = calc.Last()
                        Dim endDuration As TimeSpan = TimeSpan.FromSeconds(endValue.ValueAsDouble())
                        Console.WriteLine($"Calculated from PE: {endDuration}")
                

                 

                And by works, I mean it runs fine AND produces the expected answer of 06:50:22.  This also works for if expr is changed to:

                 

                        Dim expr As String = $"TimeEq('{tag.Name}', '{startTime.ToString("dd-MMM-yy")}', '{endTime.ToString("dd-MMM-yy")}', ""{yourDesiredStateName}"")"
                

                 

                However, if I try change the format string to "dd-MMM-yy hh:mm:ss", I do not get an error but I get the wrong answer.  Using a format string of "dd-MMM-yy hh:mm:ss tt" to include the AM or PM designation, throws an exception.  After some trial and error, the other thing that works is "dd-MMM-yy HH:mm:ss".

                  • Re: AF SDK fetch digital tag value
                    SyedHussain

                    Hi Rick and Rajan,

                     

                    Appreciate and thanks for the support. Rick is correct i am using AF SDK in writing a scheduler for a large process for oil & gas hence i need to use AF SDK only. But i used the last example given by rick as shown below, but looks like its generating error. It does not reach the last writeentry and comes off in try..catch.

                     

                    IPDailyLog.WriteEntry("IPDailyEnv-" & eventCounter & ": before digTagTime " & mydahsServerName & "/ ValveStatus" & myValveOpenState, EventLogEntryType.SuccessAudit, 2115)

                    eventCounter = eventCounter + 1

                     

                    myTimeRange.StartTime = StartDt

                    myTimeRange.EndTime = EndDt

                     

                    expr = $"TimeEq('{" & mytagname & "}', 'y', 't', ""{ "& myValveOpenState & "}"")"

                    Dim calc = AFCalculation.CalculateAtIntervals(myPIServer, expr, myTimeRange, New AFTimeSpan(myTimeRange.Span))

                    Dim endValue As OSIsoft.AF.Asset.AFValue = calc.Last()

                    endDuration = TimeSpan.FromSeconds(endValue.ValueAsDouble())

                    IPDailyLog.WriteEntry("IPDailyEnv-" & eventCounter & ": after digTagTime " & expr & "/ Total Valvetime" & endDuration.Hours, EventLogEntryType.SuccessAudit, 2115)

                    eventCounter = eventCounter + 1

                     

                    thanks and appreciate

                    syed

                      • Re: AF SDK fetch digital tag value
                        Rick Davin

                        Hi Syed,

                         

                        You should not have altered the expr string other than replacing your variable names.  You are using an Interpolated String (prefaced with $ ) so there is no reason to clutter the expr with ampersands.

                         

                        Change this line:    expr = $"TimeEq('{" & mytagname & "}', 'y', 't', ""{ "& myValveOpenState & "}"")"

                         

                        To this exact string:    $"TimeEq('{mytagname}', 'y', 't', ""{myValveOpenState}"")"

                         

                        Also let's add one correction for endDuration:

                         

                             Dim endDuration As TimeSpan = If(endValue.IsGood, TimeSpan.FromSeconds(endValue.ValueAsDouble()), TimeSpan.Zero)

                         

                        Finally you should take caution of using TimeSpan.Hours as it returns only the whole number portion.  You probably want to use the TotalHours property as can format the 1 or 2 decimal places sufficient for logging.  Example:

                         

                        IPDailyLog.WriteEntry("IPDailyEnv-" & eventCounter & ": after digTagTime " & expr & "/ Total Valvetime" & endDuration.TotalHours.ToString("N2"), EventLogEntryType.SuccessAudit, 2115)

                         

                        Or you can use string interpolation again to use this equivalent line of code:

                         

                        IPDailyLog.WriteEntry($"IPDailyEnv-{eventCounter}: after digTagTime {expr}/ Total Valvetime {endDuration.TotalHours:N2}", EventLogEntryType.SuccessAudit, 2115)

                  • Re: AF SDK fetch digital tag value
                    SyedHussain

                    Hi Rick

                     

                    While testing on test server the code ran smoothly but on production its failed for one of the tag with following error..appreciate and thanks

                    IPDailyEnv-61966: Method not found: 'Double OSIsoft.AF.Asset.AFValue.ValueAsDouble()'. at IPDailySchedule.Module1.getRecoveryValveOpenedTime(String myConnectionString,Datetime StartDt, Datetime EndDt, String HeaderID) at IPDailySchedule.Module1.Main()

                     

                    My code is as follows

                    myCurrentValveState = "OPEN"

                    myPIServer = PIServer.FindPIServer(mydahsServerName)

                    myPIServer.Connect()

                    localPIServerinstance = myPIServer.ConnectionInfo

                     

                     

                    If localPIServerinstance.IsConnected = True Then

                          myPoint = PIPoint.FindPIPoint(myPIServer, mytagname.Trim)

                          If myPoint.IsResolved = True Then

                                  myTimeRange.StartTime = StartDt

                                  myTimeRange.EndTime = EndDt

                                  expr = $"TimeEq('{mytagname}', 'y', 't', ""{myValveOpenState}"")"

                                  calc = AFCalculation.CalculateAtIntervals(myPIServer, expr, myTimeRange, New AFTimeSpan(myTimeRange.Span))

                                  endValue = calc.Last()

                                  endDuration = If(endValue.IsGood, TimeSpan.FromSeconds(endValue.ValueAsDouble()), TimeSpan.Zero)

                          Else

                                  myCurrentValveState = "CLOSE"

                         End If

                    Else

                           myCurrentValveState = "CLOSE"

                    End If

                     

                    Regards

                    Syed