/*--------------------------------------------------------------------
* 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());
}