14.14. Photon: Sending Queries - send_query, .assign_values, .get_data

The send_query function calls the .assign_values method to get the currently-entered values from the various entry widgets, and then sends two queries. The first query is a set-up query, which is used to find the maximum and minimum X and Y values for the interpolated data. Those values are then stored in a list.

   /*--------------------------------------------------------------------
    * Function:    send_query
    * Returns:     t or nil
    * Description: Calls the necessary functions to send a query to the
    *              Historian, and handles the data returned.  It is activated
    *              by the "Record" button popping out, or any radio button.
    *------------------------------------------------------------------*/
   function send_query(iset, text, parent, ent1, ent2,
                       ent3, ent4, ent5, ent6, button)
   {
     local fnholder, datalist, minmaxlist;
     
     if ((button == hw.NoIntBut || button == hw.PeriodBut ||
          button == hw.RelBut || button  == hw.FixRelBut ||
          button == hw.DisplayBut || button  == hw.RecBut))
       iset.dbflag = "NONE";
     
     iset.assign_values(ent1, ent2, ent3, ent4, ent5, ent6);
     
     datalist = list();
     fnholder = iset.fn;
     
     /* Assign a name for the setup query. */
     if ((iset.fn == "NoInterpolator") || (iset.fn == "Periodic"))
       iset.fn = "TimeSetup";
     else
       iset.fn = "RelSetup";
   

There are two possible setups: time and relative, depending on what the X-axis represents. The NoInterpolator and Periodic options plot a single history against time, and thus give time-based output. The Relative and FixedRelative options plot one history against another, and thus require the X-axis to have values ranging from 0 to 200.

To create the largest plot possible in the given space, we need to set the range of our axes to the minimums and maximums of the data set. This information is obtained using the min_max function.

     /* Make the setup query and assign minimum and maximum values. */
     datalist = iset.get_data();
     if (car(datalist) && cadr(datalist))
       minmaxlist = list(min_max(car(datalist)), min_max(cadr(datalist)));
     else
       minmaxlist = nil;
     
     /* Make the actual query. */
     iset.fn = fnholder;
     datalist = iset.get_data();
     show_data(text, iset, parent, button, datalist, minmaxlist);
   }
   

Both the first and second query bring in their data using the .get_data method (below). The second query then shows the actual requested query data using the show_data function.

This method is called by send_query to get the current values from the InterpolatorSettings class before actually sending a query. It uses the common function assign_history to assign the y_history and x_history.

   /*--------------------------------------------------------------------
    * Method:      InterpolatorSettings.assign_values
    * Returns:     t or nil
    * Description: Assigns the values from the interpolator entry widgets
    *              to the appropriate instance variables of the class.
    *------------------------------------------------------------------*/
   method InterpolatorSettings.assign_values(e1, e2, e3, e4, e5, e6)
   {
     .y_history = assign_history(.dbflag, .y_history, e1.text_string);
     .start = number(e2.text_string);
     .duration = number(e3.text_string);
     .x_history = assign_history(.dbflag, .x_history, e4.text_string);
     .interval = number(e5.text_string);
     .max_gap = number(e6.text_string);
   }
   

The .get_data method of the InterpolatorSettings class sends a query to the Cascade Historian, using an interpolate command. The interpolator used is determined by calling the .set_interpolator method. The Cascade Historian returns that interpolated query as a list of lists, with each sub-list containing a pair of numbers. The .get_data method extracts those numbers and writes them in two columns in the output file. This method then creates and returns a list of two sub-lists, each of which contains all the values for one column. Why bother writing the data to a file when we have it in memory as a list? Because for plotting we need access to the query results of all three deadband options at any given time.

   /*--------------------------------------------------------------------
    * Method:      InterpolatorSettings.get_data
    * Returns:     a list of x-data and y-data, as strings
    * Description: Sends a query to the Historian and receives the data.
    *              Activated by the send_query function.  
    *------------------------------------------------------------------*/
   method InterpolatorSettings.get_data()
   {
     local tsk, intpl, buf_length, alldata, count, dur, data, outfile;
     local xdata = list();
     local ydata = list();
     local midnite = find_midnite();
   

There are several ways that this function might fail. For example, the Cascade Historian might send an error message instead of the requested data. The .set_interpolator function that receives that message will also throw an error. This error is contained here by this try/catch block. The catch statement (at the end of the function) simply sends nil instead of data.

     try
       {
         /* Make sure there are valid values in the entry widgets.*/
         if (undefined_p(.start) || (!number_p(.start)))
           {
             .start = 0;
             hw.StartEnt.text_string = "0";
           }
         if (undefined_p(.duration) || (!number_p(.duration))
             || (.duration <= 0))
           {
             .duration = 10;
             hw.DurEnt.text_string = "10";
           }
         if (undefined_p(.interval) || (!number_p(.interval))
             || (.interval < .01))
           {
             .interval = .01;
             hw.IntEnt.text_string = "0.01";
           }

We test the values entered in three of the entry widgets, using Gamma's undefined_p and number_p predicate functions.

Next we we write the data to the output file.

         
         if ((tsk = locate_task("demohistdb", nil)) != nil)
           {
             intpl = self.set_interpolator(tsk);
             
             buf_length = caddr(send(tsk, `bufferIdLength(intpl)));
             if(!((.fn == "TimeSetup") || (.fn == "RelSetup")))
               hw.NumPts.text_string = string(buf_length);
             
             alldata = list();
             count = 0;
             
             while (count < buf_length)
               {
                 if((buf_length - count) > 100)
                   dur = 100;
                 else
                   dur = buf_length - count;
                 data = send(tsk, `bufferIdDataAscii(intpl, @count, @dur));
                 data = cddr(cdr(data));
                 alldata = append(alldata, data);
                 count = count + 100;
               }
             

Since the interpolated data set can be very large, potentially requiring long processing times, we used the Cascade Historian's bufferIdLength command to calculate the length of the buffer, which then allows us to process it in chunks. The chunks can be any size; we chose a size of 100 values. For each chunk we send a bufferIdDataAscii command to get the values in ASCII format.

             if (outfile = open("/tmp/cogentdemo/hsoutput.dat", "w"))
               if (buf_length == 0)
                 {
                   writec(outfile, "# Sorry, no data matched your query.");
                   writec(outfile, string("\n", midnite, " 0",
                                          "\n", (midnite + 1), " 0"));
                   close(outfile);
                 }
               else
                 {
                   with d in alldata do
                     writec(outfile, string(car(d), " ", cadr(d), "\n"));
                   close(outfile);
                 }
             
             while (alldata != nil)
               {
                 if(.fn == "TimeSetup" ||
                    .fn == "NoInterpolator" || .fn == "Periodic")
                   xdata = cons(caar(alldata) - midnite, xdata);
                 else
                   xdata = cons(caar(alldata), xdata);
                 ydata = cons(cadar(alldata), ydata);
                 alldata = cdr(alldata);
               }
             xdata = reverse(xdata);
             ydata = reverse(ydata);
             send(tsk, #bufferIdDestroy(intpl)); 
           }
         list(xdata, ydata);
       }
     catch
       list(nil, nil);
   }
   

In addition to the written output file, we also want to have the data available as a list, to be returned to the send_query function (above) for further use. After gathering the data for the list, we have no more use for the data buffer, and so destroy it to free up memory, using the Cascade Historian's bufferIdDestroy command. Then we create the data list using the Gamma list function, and it becomes the return value of the function.