GTK: Sending Queries - send_query, .get_data

Cogent Tools Demo and Tutorials: Version 4.0
Prev | Chapter 14. History Functions | Next

# 14.6. GTK: Sending Queries - `send_query`, `.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 handle the data returned.  It is activated
        *              by the "Record" button popping out, or any radio button.
        *              It sends two queries, the first to calculate the minimum
        *              and maximum values for use in creating plots, and the
        *              second to get the actual data.
        * Arguments:   button:       The button that activated the function
        *              iset:         An InterpolatorSettings instance
        *              e1 - e6:      The six Interpolator options entry widgets
        *              e7:           The "Number of points from last query" display
        *              b1 - b4:      The four Interpolator options buttons
        *              b5:           The Record button
        *              b6:           The Update display button
        *------------------------------------------------------------------*/
       function send_query(button, iset, text, e1, e2, e3, e4,
                           e5, e6, e7, b1, b2, b3, b4, b5, b6)
       {
         local fnholder, datalist, minmaxlist;
         if ((button == nil) || (button == b6) || (button.switched_on()))
           {
             if ((button == b1 || button == b2 ||
                  button == b3 || button == b4 ||
                  button == b5 || button == b6))
               iset.dbflag = "NONE";

             iset.assign_values(e1, e2, e3, e4, e5, e6);

             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(e1, e2, e3, e4, e5, e6, e7);
             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(e1, e2, e3, e4, e5, e6, e7);
             show_data(text, iset, button, datalist, minmaxlist);
           }
         else
           {
             write_minmax(iset.misc_info);
           }
       }

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. The final `else` statement is not necessary when this function gets called by pressing the Record button, or any of the Interpolator options buttons. But when it gets called by one of the Deadband options buttons, it is necessary to write the minimum and maximum values to the `plhistsetup.dat` file for gnuplot. This is done with the `write_minmax` function.

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 two lists
        * Description: Sends the query and retrieves the data.  Activated by
        *              the send_query() function.
        *------------------------------------------------------------------*/
       method InterpolatorSettings.get_data(e1, e2, e3, e4, e5, e6, e7)
       {
         local tsk, intpl, buf_length, alldata, count, dur, data, outfile;
         local xdata = list();
         local ydata = list();
         local midnite = find_midnite();

         /* Make sure there are valid values in the entry widgets.*/
         if (undefined_p(.start) || (!number_p(.start)))
           {
             .start = 0;
             e2.set_text("0");
           }
         if (undefined_p(.duration) || (!number_p(.duration))
             || (.duration <= 0))
           {
             .duration = 10;
             e3.set_text("10");
           }
         if (undefined_p(.interval) || (!number_p(.interval))
             || (.interval < .01))
           {
             .interval = .01;
             e5.set_text("0.01");
           }

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

         if ((tsk = locate_task("demohistdb", nil)) != nil)
           {
             alldata = list();
             count = 0;
             intpl = self.set_interpolator(tsk);
             buf_length = caddr(send(tsk, `bufferIdLength(intpl)));
             if(!((.fn == "TimeSetup") || (.fn == "RelSetup")))
               e7.set_text(string(buf_length));

             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.

Next we choose an output file, based on the deadband selected in the Deadband options, and then after checking to make sure there is data, we write the data to the output file.

             if(.dbflag == "NONE")
               outfile = open("/tmp/cogentdemo/hsoutput.dat", "w");
             if(.dbflag == "DB")
               outfile = open("/tmp/cogentdemo/hsoutput2.dat", "w");
             if(.dbflag == "DBP")
               outfile = open("/tmp/cogentdemo/hsoutput3.dat", "w");
             if (outfile)
               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);
       }

Each deadband option get its own output data file (`hsoutput*.dat`), which gets rewritten each time a query is made on that deadband. All the data files in the demo are written to the `/tmp/cogentdemo/` temporary directory, and deleted when the Controller exits.

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 constructing 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.

Prev | Home | Next
GTK: Setting Up Plots - `reset_deadbands`, `plot_prep` | Up | GTK: Displaying Query Data - `show_data`, `write_minmax`, `file_add`

Copyright 1995-2002 by Cogent Real-Time Systems, Inc.