8.7. Complete program - phsimple.g

/*--------------------------------------------------------------------
 * File:        phsimple.g
 *
 * Description: Cogent Tools Demo Tutorial One for Photon.
 *
 * Functions:
 *              start_qnserves
 *              program_startup
 *              toggle_sym          
 *              change_settings
 *              slider_callback     
 *              num_callback
 *              accumulate_trends
 *              update_trends       
 *              numeric_assign      
 *              create_monitor      
 *              main
 *------------------------------------------------------------------*/

/********************************************************
 *                     STARTING UP                      *
 ********************************************************/

require_lisp("PhotonWidgets.lsp");
require_lisp("PhabTemplate.lsp");
PtInit(nil);

/*--------------------------------------------------------------------
 * Function:    start_qnserves
 * Returns:     t on success, or nil
 * Description: Used to start qserve or nserve, as specified.  Called by
 *              the program_startup() function.
 *------------------------------------------------------------------*/
function start_qnserves(command, name)
{
  for(i=0; i<=50; i++)
    {
      if ((_os_ == "Linux") || (_os_ == "QNX4"))
        if ((qnx_name_locate(0, name, 0) == nil))
          {
            system(command);
            usleep(10000);
          }
        else
          {
            i = 50;
            nil;
          }
      else
        if (access(string("/dev/", command), 0) == -1)
          {        
            system(command);
            usleep(10000);
          }
        else
          {
            i = 50;
            nil;
          }
    }
}     

/*--------------------------------------------------------------------
 * Function:    program_startup
 * Returns:     t or nil
 * Description: Starts any necessary Cogent software and the main window.
 *              Called by the main() function.
 *------------------------------------------------------------------*/
function program_startup (proc_name, queue_name, win_fn)
{
  local window, tsk;
  
  /* See if qserve and nserve are running. If not, start them. */
  start_qnserves("qserve", "qserve");
  start_qnserves("nserve", "sc/nserve");
  
  /* Start interprocess communication. */
  if (init_ipc(proc_name, queue_name, "toolsdemo") == nil)
    error("Could not initialize IPC.");
  
  /* See if a DataHub is running in the toolsdemo domain.
     If not, start it and a DataHub Viewer. */
  if ((tsk = locate_task ("/dh/toolsdemo", t)) == nil)
    system("datahub -d toolsdemo");
  else
    close_task (tsk);
  
  if ((pid_dhview = fork()) == 0)        
    {
      /* This is the child process. */
      exec ("phdhview", "-d", "toolsdemo");      
      /* In case the above exec commands fails, the following will be
       * called, and it's virtually guaranteed not to fail.  Thus in
       * any circumstance a child process gets created.*/
      exec("/bin/true");
    }
  
  /* Make sure the PID Emulator is not already running. */
  if ((tsk = locate_task ("emul", t)) != nil)
    {
      send(tsk, #exit_program(0));
      close_task (tsk);
    }

  /* Start the PID Emulator. */
  if ((pid_emul = fork()) == 0)        
    {
      /* This is the child process. */
      exec ("gamma", "emul");      
      exec("/bin/true");
    }
  
  /* Kill child processes when this program exits. */
  atexit(`kill(pid_dhview, SIGINT));
  atexit(`kill(pid_emul, SIGINT));
  
  /* Create and register some points with the datahub. */
  usleep(10000);
  SP_001 = register_point (#SP_001);
  PV_001 = register_point (#PV_001);
  MV_001 = register_point (#MV_001);
  AUTO_001 = register_point (#AUTO_001);
  FREQ_001 = register_point (#FREQ_001);
  PID1_Kp = register_point (#PID1_Kp);
  PID1_Ki = register_point (#PID1_Ki);
  PID1_Kd = register_point (#PID1_Kd);
  PROP_001 = register_point (#PROP_001);
  INT_001 = register_point (#INT_001);
  
  /* Create the main window, and put in a destructor function for GTK */
  window = eval(win_fn);
}


/********************************************************
 *                     THE MONITOR                      *
 ********************************************************/

/*--------------------------------------------------------------------
 * Function:    toggle_sym
 * Returns:     t or nil
 * Description: Toggles a database point between 0 and 1 when a button
 *              is pressed.
 *------------------------------------------------------------------*/
function toggle_sym(sym)
{
  set (sym, eval(sym) == 0 ? 1 : 0);
  write_point (sym, eval(sym));
}

/*--------------------------------------------------------------------
 * Function:    change_settings
 * Returns:     t or nil
 * Description: Used by the Monitor to change PID settings, and optionally
 *              unclick the Auto button if it is on.  The numeric widgets
 *              won't execute callbacks after they have been changed
 *              programmatically, so we have to write the point using
 *              write_point().
 *------------------------------------------------------------------*/
function change_settings(auto, n1, v1, n2, v2, n3, v3, n4, v4,
                         n5, v5, n6, v6, msgno, autobutton?)
{
  if (auto == nil)
    {
      if (AUTO_001 != 0)
        {
          autobutton.onoff_state = 0;
          write_point(#AUTO_001, 0);
        }
    }
  else
    {
      autobutton.onoff_state = 1;
      write_point(#AUTO_001, 1);
      n1.numeric_value = v1;
      write_point(#FREQ_001, v1);
    }
  
  if (_os_ == "QNX4")
    {
      n2.numeric_value = v2;
      n3.numeric_value = v3;
      n4.numeric_value = v4;
      n5.numeric_value = v5;
      n6.numeric_value = v6;
    }
  else
    {
      n2.numeric_value = v2 * 100;
      n3.numeric_value = v3 * 100;
      n4.numeric_value = v4 * 100;
      n5.numeric_value = v5 * 100;
      n6.numeric_value = v6 * 100;
    }  
  write_point(#PID1_Kp, v2);
  write_point(#PID1_Ki, v3);
  write_point(#PID1_Kd, v4);
  write_point(#PROP_001, v5);
  write_point(#INT_001, v6);
}

/*--------------------------------------------------------------------
 * Function:    slider_callback
 * Returns:     t or nil
 * Description: Attaches a callback and adds an exception function to
 *              a slider widget.  The callback writes a value to the
 *              datahub point specified, and the exception function
 *              assigns the value of the datahub point to the slider
 *              widget whenever the value of the point changes in the
 *              datahub.
 *------------------------------------------------------------------*/
function slider_callback(sld, !pnt)
{
  PtAttachCallback(sld, Pt_CB_SLIDER_MOVE,
                   `write_point(@pnt, ((@sld).gauge_value)));
  add_exception_function(eval(pnt),
                         `(@sld).gauge_value = (@eval(pnt)));
}

/*--------------------------------------------------------------------
 * Function:    num_callback
 * Returns:     t or nil
 * Description: Sets up a callback and an exception function for PtNumeric
 *              widgets to deal with sending changed values to and from
 *              the datahub.  Assigns initial values to the widgets.
 *------------------------------------------------------------------*/
function num_callback(num, !pnt)
{
  if(_os_ == "QNX4")
    {
      PtAttachCallback(num, Pt_CB_NUMERIC_CHANGED,
                       `write_point(@pnt, ((@eval(num)).numeric_value)));
      pnt = eval(pnt);
      num.numeric_value = eval(pnt);
      add_exception_function(pnt, `(@num).numeric_value = eval(@pnt));
    }
  else
    {
      
      /* Handle an annoying characteristic in Photon 2 (OS is QNX 6) whose
         PtNumericFloat widget does not work properly in Gamma. Thus we need
         to use a PtNumericInteger widget, and convert back and forth to write
         points and receive exceptions. The widget that corresponds to FREQ_001
         is a PtNumericInteger, which is already an integer.*/
      
      if (eval(pnt) != #FREQ_001)
        {
          PtAttachCallback(num, Pt_CB_NUMERIC_CHANGED,
                           `write_point(@pnt, ((@eval(num)).numeric_value) / 100.0));
          pnt = eval(pnt);
          num.numeric_value = round(eval(pnt) * 100);
          add_exception_function(pnt, `((@num).numeric_value = round(eval(@pnt) * 100)));
        }
      else
        {
          PtAttachCallback(num, Pt_CB_NUMERIC_CHANGED,
                           `write_point(@pnt, ((@eval(num)).numeric_value)));
          pnt = eval(pnt);
          num.numeric_value = eval(pnt);
          add_exception_function(pnt, `(@num).numeric_value = eval(@pnt));
        }
    }
}

/* Assign each DataHub point a property value, which in this case is an
 * array.  These arrays will be used to hold a short history of each point,
 * allowing them to be plotted by the trend widget.
 */
setprop (#SP_001, #tdata, make_array (0));
setprop (#MV_001, #tdata, make_array (0));
setprop (#PV_001, #tdata, make_array (0));

/*--------------------------------------------------------------------
 * Function:    accumulate_trends
 * Returns:     t or nil
 * Description: Adds new data to property value arrays.  These arrays
 *              are property values for the variables that correspond
 *              to the DataHub points, and are used to update the trend
 *              widget.
 *------------------------------------------------------------------*/
function accumulate_trends (syms...)
{
  local data;
  with sym in syms do
    {
      data = getprop (sym, #tdata);
      data[length(data)] = eval (sym);
    }
}

/*--------------------------------------------------------------------
 * Function:    update_trends
 * Returns:     t or nil
 * Description: Assigns the most recent arrays to the trend widget.
 *------------------------------------------------------------------*/
function update_trends (widget, syms...)
{
  local        tarray = make_array (length(syms)), i = 0, sym;
  
  with sym in syms do
    tarray[i++] = getprop (sym, #tdata);
  if(_os_ == "QNX4")
    widget.rttrend_data = tarray;
  else
    widget.trend_data = tarray;
  
  with sym in syms do
    shorten_array (getprop (sym, #tdata), 0);
}

/*--------------------------------------------------------------------
 * Function:    numeric_assign
 * Returns:     t or nil
 * Description: Converts DataHub entries to integer values for use in
 *              PtNumericInteger widgets.  This is necessary because the
 *              an irregularity in the Photon 2 PtNumericFloat widget
 *              prevents it from working properly in Gamma.
 *------------------------------------------------------------------*/
function numeric_assign(num1, num2, num3, num4, num5)
{
  if(_os_ == "QNX4")  
    nil;
  else
    {
      num1.numeric_value = round(read_point(#PID1_Kp) * 100);
      num2.numeric_value = round(read_point(#PID1_Ki) * 100);
      num3.numeric_value = round(read_point(#PID1_Kd) * 100);
      num4.numeric_value = round(read_point(#PROP_001) * 100);
      num5.numeric_value = round(read_point(#INT_001) * 100);
    }
}

/*--------------------------------------------------------------------
 * Function:    create_monitor
 * Returns:     doesn't return
 * Description: Creates the Monitor window.
 *------------------------------------------------------------------*/
function create_monitor ()
{
  local wfile, window, monitor_win, slidesp, progmv, progpv;
  local slideauto, slidedp, numkp, numki, numkd, numprop, numint;
  local butam, butgood, butpoor, butosc, butout, butx;
  
  if (_os_ == "QNX4")
    wfile = PhabReadWidgetFile("phmonitorwin/wgt/Ptmonitor.wgtw");
  else
    wfile = PhabReadWidgetFile("ph2monitorwin/wgt/Ptmonitor.wgtw");

  window = PhabCreateWidgets(wfile, nil, nil);
  monitor_win = PhabLookupWidget(window, #Ptmonitor, nil);
  slidesp = PhabLookupWidget(window, #PtSliderSP, nil);
  slideclr = PhabLookupWidget(window, #RtProgressColor, nil);
  numfreq = PhabLookupWidget(window, #PtNum1, nil);
  numkp = PhabLookupWidget(window, #PtNum2, nil);
  numki = PhabLookupWidget(window, #PtNum3, nil);
  numkd = PhabLookupWidget(window, #PtNum4, nil);
  numprop = PhabLookupWidget(window, #PtNum5, nil);
  numint = PhabLookupWidget(window, #PtNum6, nil);
  
  butgood = PhabLookupWidget(window, #PtToggleGood, nil);
  butpoor = PhabLookupWidget(window, #PtTogglePoor, nil);
  butosc = PhabLookupWidget(window, #PtToggleOscillating, nil);
  butout = PhabLookupWidget(window, #PtToggleOOC, nil);
  butam = PhabLookupWidget(window, #PtOnOffButtonAutomode, nil);
  labprop = PhabLookupWidget(window, #PtLabelPropVal, nil);
  progmv = PhabLookupWidget(window, #ProgressMV, nil);
  progpv = PhabLookupWidget(window, #ProgressPV, nil);
  trend1 = PhabLookupWidget(window, #Trend1, nil);
  
  slidesp.gauge_value = read_point(#SP_001);
  slideclr.gauge_value = read_point(#SP_001);
  progmv.gauge_value = read_point(#MV_001);
  progpv.gauge_value = read_point(#PV_001);
  butam.onoff_state = read_point(#AUTO_001);
  numfreq.numeric_value = read_point(#FREQ_001);

  numeric_assign(numkp, numki, numkd, numprop, numint);
  
  add_exception_function(#AUTO_001, `(@butam).onoff_state = AUTO_001);
  add_exception_function(#SP_001, `(@slideclr).gauge_value = SP_001);
  add_echo_function(#SP_001, `(@slideclr).gauge_value = SP_001);
  add_exception_function(#MV_001, `(@progmv).gauge_value = MV_001);
  add_exception_function(#PV_001, `(@progpv).gauge_value = PV_001);
  
  slider_callback (slidesp, #SP_001);
  
  num_callback (numfreq, #FREQ_001);
  num_callback (numkp, #PID1_Kp);
  num_callback (numki, #PID1_Ki);
  num_callback (numkd, #PID1_Kd);
  num_callback (numprop, #PROP_001);
  num_callback (numint, #INT_001);
  
  PtAttachCallback(butgood, Pt_CB_ACTIVATE,
                   `change_settings(t, @numfreq, 4, @numkp, .05,
                                    @numki, .45, @numkd, .05,
                                    @numprop, .7, @numint, 1.0,
                                    3.8, @butam));
  
  PtAttachCallback(butpoor, Pt_CB_ACTIVATE,
                   `change_settings(t, @numfreq, 10, @numkp, .05,
                                    @numki, .70, @numkd, .05,
                                    @numprop, .7, @numint, 5.0,
                                    3.8, @butam));
  
  PtAttachCallback(butosc, Pt_CB_ACTIVATE,
                   `change_settings(nil, @numfreq, 10, @numkp, .05,
                                    @numki, 1.37, @numkd, .05,
                                    @numprop, .7, @numint, .54,
                                    3.8, @butam));
  
  PtAttachCallback(butout, Pt_CB_ACTIVATE,
                   `change_settings(nil, @numfreq, 10, @numkp, .05,
                                    @numki, .45, @numkd, 1.00,
                                    @numprop, .7, @numint, .54,
                                    3.8, @butam));
  
  PtAttachCallback(butam, Pt_CB_ONOFF_NEW_VALUE, `toggle_sym(#AUTO_001));
  
  butx = PhabLookupWidget(window, #Ptxbut, nil);
  PtAttachCallback(butx, Pt_CB_ACTIVATE, #exit_program(-1));
  
  /* Set up the trend widget.  We must set the trend resources in the
   * order below to ensure that the trend widget knows what to do with
   * the line colors.  We want 3 traces, in the same colors that the
   * designer set when creating the slider and progress bars in the
   * Photon App Builder.
   */
  trend1.trend_count = 3;
  trend1.trend_color_list = array (slideclr.progress_bar_color,
                                   progmv.progress_bar_color,
                                   progpv.progress_bar_color);
  trend1.trend_attributes = array (1, 2, 3);
  
  /* Animate trends every tenth of a second.  The trend data arrays are
   * stored in the property lists of the associated database variables.
   * We can accumulate trend data faster than we display it by changing
   * the rates of accumulation and update.  It does not make sense to display
   * trend data faster than about 27Hz, because the eye cannot distinguish
   * anything beyond that from smooth motion.  In reality, 10Hz is plenty.
   */
  every (0.1, #accumulate_trends (#SP_001, #MV_001, #PV_001));
  every (0.1, #update_trends (trend1, #SP_001, #MV_001, #PV_001));
  
  monitor_win.SetPos(20, 250);
  PtRealizeWidget(monitor_win);
  
  //  PtMainLoop();
  while(t) next_event();
  
}

/*--------------------------------------------------------------------
 * Function:    main
 * Returns:     doesn't return
 * Description: Calls the program_startup() function.
 *------------------------------------------------------------------*/
function main()
{
  program_startup("monitor", "monq", #create_monitor());
}