11 Replies Latest reply on Feb 19, 2015 7:36 AM by vincent.spaa

    How can I handle a call to GetValues in a custom DR?

    vincent.spaa

      I have a custom data reference which can handle a call to GetValue (i.e. a request for a single value) just fine.

      But at some point the customer also wanted to trend the values so (from what I understand) support for a call to GetValues is then also required to make it work.

      So I wrote a GetValues method like so:

       

      public override AFValues GetValues(object context, AFTimeRange timeRange, int numberOfValues,
          AFAttributeList inputAttributes, AFValues[] inputValues)
      {
          AFValues valuesCollection = new AFValues();
          DateTime start = timeRange.StartTime.LocalTime;
          start = new DateTime(start.Year, start.Month, start.Day, start.Hour, 0, 0, 0)
          DateTime end = timeRange.EndTime.LocalTime;
          end = new DateTime(end.Year, end.Month, end.Day, end.Hour, 0, 0, 0);
      
          while (start < end)
          {
              valuesCollection.Add(Calculate(start));
              start = start.AddHours(1);
          }
      
          return valuesCollection;
      }
      

       

      Since the values only change every hour (at the exact hour) I figured I could work with increments of an hour.

      However, when I try to trend the values from my custom DR, I do get a call to GetValues() but the timeRange stays the same. When I change the time range in Processbook, I still get a timeRange passed to GetValues which only spans 8 hours.

      Even if I enter *-256h and * for the range in Processbook, I still get a value for timeRange which only spans 8 hours (from 8 hours ago until now).

       

      Is there something I'm doing wrong?

       

      This is how my Supported properties look like:

       

      public override AFDataReferenceContext SupportedContexts
      {
        get { return AFDataReferenceContext.All; }
      }
      
      
      public override AFDataReferenceMethod SupportedMethods
      {
        get
        {
        return AFDataReferenceMethod.GetValue | AFDataReferenceMethod.GetValues;
        }
      }
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
        • Re: How can I handle a call to GetValues in a custom DR?
          Rick Davin

          How many attributes are returned from GetInputs()?  Zero?  One?  More than one?

           

          Your Calculate() method requires a DateTime parameter with Kind==Local.  I'd suggest that when working with AFValues that its best to use AFTime rather than DateTime.

           

          If you have input attributes, your GetValues() seems to try to do too much.  When your GetValues() method is called, GetInputs() will have been called, and any input attributes would also have had its data retrieved for the requested TimeContext.

           

          If GetInputs() returned no attributes, then inputValues array would have nothing in it.  In this case, your code looks okay.  But if GetInputs() does rely upon input attributes, I'd suggest that you rely on data already fetched with inputValues.

           

          Example IF GetInputs() returns ONE attribute:

           

              public override AFValues GetValues(object context, AFTimeRange timeRange, int numberOfValues,
              AFAttributeList inputAttributes, AFValues[] inputValues)
              {
                  AFValues valuesCollection = new AFValues();
                  foreach (var value in inputValues[0])
                  {
                      valuesCollection.Add(Calculate(value.Timestamp.LocalTime));
                  }
                  return valuesCollection;
              }
          

           

          While you may think you have evenly spaced 1-hour intervals, keep in mind the actually start and end time do not have align evenly on those intervals.

          1 of 1 people found this helpful
          • Re: How can I handle a call to GetValues in a custom DR?
            Rick Davin

            Couple of points to keep in mind.

             

            1. The timeRange StartTime and EndTime do not have to align on whole number of hours.
            2. You should output values at the StartTime and EndTime events.
            3. If your Calculate method depends on local times, be sure you always deal with local time.

             

            Code sample:

             

               

            public override AFValues GetValues(object context, AFTimeRange timeRange, int numberOfValues,
                                                   AFAttributeList inputAttributes, AFValues[] inputValues)
                {
                    AFValues valuesCollection = new AFValues();
                    DateTime start = timeRange.StartTime.LocalTime;
                    // This adjustment may back up before timeRange.StartTime
                    start = new DateTime(start.Year, start.Month, start.Day, start.Hour, 0, 0, 0, DateTimeKind.Local);
                    DateTime end = timeRange.EndTime.LocalTime;
                    end = new DateTime(end.Year, end.Month, end.Day, end.Hour, 0, 0, 0, DateTimeKind.Local);
            
                    // Remember timeRange.StartTime and .EndTime do not always coincide on even hours
                    // but we should still include them in our output.
                    valuesCollection.Add(Calculate(timeRange.StartTime.LocalTime));
                    if (start <= timeRange.StartTime.LocalTime)
                    {
                        start = start.AddHours(1);
                    }
            
                    while (start < end)
                    {
                        valuesCollection.Add(Calculate(start));
                        start = start.AddHours(1);
                    }
            
                    if (valuesCollection.Last().Timestamp < timeRange.EndTime)
                    {
                        valuesCollection.Add(Calculate(timeRange.EndTime.LocalTime));
                    }
                 
                   return valuesCollection;
                }
            
            
            1 of 1 people found this helpful
              • Re: How can I handle a call to GetValues in a custom DR?
                Rick Davin

                Me again.  My previous answer should work for you.  If not, chances are the issue lies with your Calculate method.  Previously I suggested passing an AFTime rather than a DateTime, as this can eliminate confusion regarding the DateTimeKind.  Let me offer some examples.

                 

                Consider this trivial example using a DateTime for input.

                 

                private AFValue Calculate(DateTime time)
                {
                    // Assumes input time has Kind==Local.
                    // I don't check it here to keep this example simple, but if I did check it the code would be messier.
                    // Are checks needed?  Well earlier examples had instances where the input time was Kind==Unspecified.
                    return new AFValue(attribute: Attribute, newValue: time, timestamp: new AFTime(time.ToUniversalTime()));
                }
                
                

                 

                Using an AFTime instead requires no cleaning and is quite clear:

                 

                private AFValue Calculate(AFTime time)
                {
                    // Much cleaner and complete as-is.
                    return new AFValue(attribute: Attribute, newValue: time.LocalTime, timestamp: time);
                }
                
                

                 

                But to help you out with your original, you will need some helpful extension methods.  Note I prefer to deal internally with UTC because it is faster and not ambiguous:

                 

                public static class TimeExt
                {
                    public static AFTime AddHours(this AFTime time, double value)
                    {
                        return new AFTime(time.UtcSeconds + value);
                    }
                    public static AFTime TruncateToHours(this AFTime time)
                    {
                        DateTime utc = time.UtcTime;
                        return new AFTime( new DateTime(utc.Year, utc.Month, utc.Day, utc.Hour, 0, 0, DateTimeKind.Utc) );
                    }
                }
                
                

                 

                Finally, the GetValues method could now become:

                 

                public override AFValues GetValues(object context, AFTimeRange timeRange, int numberOfValues,
                                                AFAttributeList inputAttributes, AFValues[] inputValues)
                {
                    AFValues valuesCollection = new AFValues();
                
                    // Remember timeRange.StartTime and .EndTime do not always coincide on even hours 
                    // but we should still include them in our output. 
                    valuesCollection.Add(Calculate(timeRange.StartTime));
                
                    // This initialization may occur before timeRange.StartTime 
                    AFTime time = timeRange.StartTime.TruncateToHours();
                    // So we may need to adjust it
                    if (time <= timeRange.StartTime)
                    {
                        time = time.AddHours(1);
                    }
                
                    while (time < timeRange.EndTime)
                    {
                        valuesCollection.Add(Calculate(time));
                        time = time.AddHours(1);
                    }
                
                    // Special corner cases: if StartTime equals EndTime, or if EndTime happens to align on whole hour.
                    if (valuesCollection.Last().Timestamp < timeRange.EndTime)
                    {
                        valuesCollection.Add(Calculate(timeRange.EndTime));
                    }
                
                    return valuesCollection;
                }
                
                

                 

                As I mentioned above, your implementation of Calculate may be the current issue.  Whatever you can share about that help us help you better.

              • Re: How can I handle a call to GetValues in a custom DR?
                dng

                Hi Vincent,

                 

                Just to comment on your original statement, there is a base class implementation for GetValues. The base class implementation from AFSDK for GetValues works by collecting the inputs required by DR and loops to call the DR GetValue, passing the input data for each requested timestamp. The determination of implementation depends on several factors (see related discussion here - note that the referenced discussion is mainly about RDA methods). However, to decide on whether to implement your own GetValues, can you give us some information about your data reference? Does it get data from an external data source? Does it get any input attributes? What kind of calculations are performed on those inputs? If it doesn't use input attributes, that factors change the results of the calculation?

                 

                In addition, can you share your implementation of GetValue and Calculate (as Rick also requested)? How is the timestamp determined in the GetValue method (current time?) Is the calculation time equivalent to the timestamp of data reference?

                 

                Back to the final application of plotting the values on a trend, how would you like the application determine which time stamps to plot the data? Do you intend to plot at regular, hourly intervals regardless of the overall plot time range?

                  • Re: How can I handle a call to GetValues in a custom DR?
                    Rick Davin

                    regarding the base class implementation ... since his GetInputs returns no attributes, and he wants to generate events every whole number hour within the timeRange, the base class implementation probably is not sufficient.

                      • Re: How can I handle a call to GetValues in a custom DR?
                        dng

                        Yea.. I noticed that in your earlier discussion as well. Decided to include it in case he works with other custom data references that can make use of the base implementation. Thanks for the note.

                        I am still wondering what governs the change of value every hour, and whether it relies on a data source, or just the timestamp at the hour.

                          • Re: How can I handle a call to GetValues in a custom DR?
                            vincent.spaa

                            Thank you for your replies so far. Rick, you make a valid point when it comes to truncating the date and time, I will definitely keep that in mind.

                             

                            The Data Reference I'm working does not rely on any external input other than some data from two AFTable (which is not something you can indicate in the GetInputs call, right?).

                            So the base implementation of GetValues does not have any Inputvalues to work with, which is why I implemented my own GetValues.

                             

                            The implementation of the Calculate method isn't that exciting but not something I can post here as a whole.

                            It calculates which shift (as in group of people) is currently active at a factory based on the time that gets passed into it. It pulls data from a simple AFTable which holds the schedule, again based on the time that's passed in.

                            It combines the information from the schedule table and some information about holidays to conclude which crew is currently active. Where "currently" refers to the timestamp being passed in. So if a timestamp in the future or in history is requested, it will calculate the shift for that.

                             

                            My main problem is that Processbook refuses to pass anything other than a timeRange which is exactly 8 hours long (and exactly 8 hours ago up till now). When I double click on the slider and enter "*-256h" and "*", my Data Reference still gets a call with a timeRange of "*-8h" up till "*". If Processbook were to pass the right timeRange, I'm fairly certain my Data Reference would work fine. Do you have any suggestions as to how I can accomplish that?

                              • Re: How can I handle a call to GetValues in a custom DR?
                                Rick Davin

                                I can't duplicate your ProcessBook problem.  Here's what I recommend: let's attempt completely rule out that there isn't a problem with your DR.  To do that, let's get away from ProcessBook and use PISystem Explorer 2.5 or better.  Use the Time Series Data menu item with Retrieval Type of "Time Range", and Boundary Type of "Interpolated", since these closest to what ProcessBook would be using.  Play around with the Start and End times.  See what happens when your Start/End times ends on whole number of hours, and also when it doesn't end on a whole number of hours.  Choose Start/End times whose durations are greater than 8-hours.  Review the retrieved data.  If that data looks 100% accurate, then you can safely assume your GetValues method is fine.  If that is the case, I suggest you start a new thread but have the title be strictly about ProcessBook.

                                  • Re: How can I handle a call to GetValues in a custom DR?
                                    dng

                                    Nice suggestions by Rick. You can also write a short custom AF SDK application to call GetValues and print the results into the Console and see if you can successfully adjust the time range there.

                                    In addition, did you override SupportedDataMethods? Make sure you leave that to the base class implementation (which by default is none) if you did not override any RDA methods such as RecordedValues.
                                    By the way, what version of PI ProcessBook are you using?

                                      • Re: How can I handle a call to GetValues in a custom DR?
                                        vincent.spaa

                                        I got it working!

                                        Turns out the devil is in the details ... as always.

                                        Removing the override of SupportedContexts made it work. For some reason only Processbook was affected by that though since AF was able to pull all the historical values I wanted.

                                        Daphne, I marked your last post as the answer since it's the closest to the actual solution. I do really appreciate all of your replies, so thank you Daphne and Rick for the support.