2 Replies Latest reply on Mar 3, 2018 7:01 PM by ainwood

    Getting Timed Values with AF

    ainwood

      I'm relatively new to the AF framework, so I think I must be doing something completely wrong (approaching the problem in the wrong way), but I'm not sure what I should be doing instead.  I am trying to get a value of a tag at a certain timestamp.  Here is what I have:

       

      public double GetValue(string tagName, DateTime timestamp)
      {
      //Get the PIPOint
       
      PIPoint point = PIPoint.FindPIPoint(DefaultPIServer, tagName);
      
      if (point != null)
      {
      //Get the timestamp as an AFTimeStamp
       
      AFTime requiredTime = new AFTime(timestamp);
      
      //Get the value
       
      AFValue value = point.InterpolatedValue(timestamp);
      
      if (value.IsGood)
      {
      return Convert.ToDouble(value.Value);
      }
      }
      return default(double);
      }
      

      Now:  When I pass it the timestamp 02-Mar-18 01:00:00, it retrieves a value, but the value has the timestamp 02-Mar-18 14:00:00.  I have tried passing the datetime (in local time) direct to the function, or trying to set it as the AFTime (as shown above).

       

      I am locally in UTC+13. I suspect that the function is being passed as UTC Time and giving me back local time.  When I use:

       

      result = PIPoints[tagName].InterpolatedValue(timeStamp.ToUniversalTime());

       

       

      I get the right result, but the AFValue timestamp then needs to be converted from Daylight time to Standard time.  Is there an easier way to do this without all these annoying timezone conversions?  Our server is on New Zealand Standard Time, and IO just want to work in New Zealand Standard Time!

       

       

       

        • Re: Getting Timed Values with AF
          Rick Davin

          Hi Andrew,

           

          Thanks for posting your question at PI Developers Club.  As I see it, the issue boils down to the timestamp.  You are using the right method to get one time value for one PIPoint.  If you ever want values for more than 1 PIPoint, then you should explore bulk data calls.  But that's a question for another day.

           

          Here is a blog about AFTime Constructors.

           

          Your custom method is passing in a .NET DateTime object.  I can almost guarantee you that it's Kind property is set to Unspecified.  When a DateTime is passed to the AFTime constructor, anything with Kind of Unspecified is treated as UTC.  That means you passed a local time string to DateTime, hoping it would be a DateTime with Kind Local, but instead it had Kind Unspecified.  When you pass the Unspecified DateTime to AFTime constructor, the input time instantly is considered UTC, which produces the offset you witness.

           

          How do you create your DateTime object?  Do you pass it a string and use DateTime.Parse?  Or do you create it with another constructor overload?  The onus is on your - the developer - to be sure the DateTime object has the correct DateTimeKind.

           

          string localTimeString = "some local date";
          // This is BAD because the Kind is Unspecified, and AFTime will treat it as UTC.
          DateTime notLocalTime = DateTime.Parse(localTimeString);
          AFTime badAFTime = new AFTime(notLocalTime);
          // Use this instead:
          DateTime localTime = DateTime.SpecifyKind(DateTime.Parse(localTimeString), DateTimeKind.Local);
          DateTime goodAFTime1 = new AFTime(localTime);
          // Or this works great too:
          AFTime goodAFTime2 = new AFTime(localTimeString);
          
          // If at all possible, use Round-Trip (or ISO 8601 compliant) time strings:
          string roundTripString = "2018-03-02T07:42:33.7102584-06:00";
          DateTime time4 = DateTime.Parse(roundTripString);
          DateTime goodAFTime3 = new AFTime(time4);
          AFTime goodAFTime4 = new AFTime(roundTripString);
          
          // To output as a Round-Trip time string use ToString("o"):
          string timeString1 = time4.ToString("o");
          string timeString2 = goodAFTime1.ToString("o");
          

           

          This should get you going in the right direction regarding the timestamp.

           

          On another note, I see if a bad value is encountered, that is if the value is a SYSTEM digital state, you will be returning a 0.  Granted your code doesn't explicitly show 0 but 0 is what is returned with default(double).  Another possibility for you to consider it to return double.NaN to avoid confusion between a good value of 0 and a bad value of 0.  For example, what if you were using degrees Celsius?  Does the 0 mean it was freezing outside, or does the 0 mean it was a bad value?  There is no confusion if I see a NaN, however.

           

          You may consider offering several overloads that take care of the various issues:

           

          public static double GetValueAsDouble(PIPoint point, AFTime time, double resultIfBadValue = double.NaN)
          {
              AFValue pv = point.InterpolatedValue(time);
              return pv.IsGood ? pv.ValueAsDouble() : resultIfBadValue;
          }
          
          public static double GetValueAsDouble(string tagName, DateTime time, double resultIfBadValue = double.NaN)
          {
              PIPoint point = PIPoint.FindPIPoint(DefaultPIServer, tagName);
              // Treat Unspecified as Local
              if (time.Kind == DateTimeKind.Unspecified)
                  time = DateTime.SpecifyKind(time, DateTimeKind.Local);
              return GetValueAsDouble(point, new AFTime(time), resultIfBadValue);
          }
          
          public static double GetValueAsDouble(string tagName, string timeString, double resultIfBadValue = double.NaN)
          {
              PIPoint point = PIPoint.FindPIPoint(DefaultPIServer, tagName);
              AFTime time = new AFTime(timeString);
              return GetValueAsDouble(point, time, resultIfBadValue);
          }
          

           

          Now you can pass in an AFTime, or a DateTime, or even a time string.  The beauty of this design is that all 3 methods all flow into 1 method that performs the InterpolatedValue call and makes the check to assign a default double.

           

          If you have more questions, just ask!

          2 of 2 people found this helpful
            • Re: Getting Timed Values with AF
              ainwood

              Thanks for that - much appreciated.  I was surprised that the function would work when passed a universal time, yet would return a value with a local time.  Certainly different to the original PI SDK / API, and I expected the default to be using the server time zone.

               

              Re the overloads - good suggestions, thanks.  The code I posted was a simplified version, where I actually have a generic function (GetValue<T>(DateTime timestamp)), so I return default<T>.  This allows me to use the same function for string tags as well, but on further consideration, we have so few of these, it probably doesn't need this, and I could have a string function as well.  NaN is certainly tidier in cascading through to other dependent calculations than other options are!

               

              Thanks again,

               

              Andrew