PV-WAVE and IDL are closely related packages which are described as Data Visualization Tools. They consist of a general purpose interpreted language with very good graphics routines. Although not described as tools for data collection and control applications, their rapid prototyping capabilities and sophisticated graphics make them a nice environment for these applications. Both also provide easy to use widget toolkits for quickly developing GUI applications.
Although there is nearly a one-for-one match between the routines in ezcaIDL
and the EZCA library itself, the syntax of the IDL routines is not
the same as the syntax of the corresponding EZCA routines. The reason for this
is that IDL is more "object oriented" and relieves the programmer of much of
the detailed bookkeeping required of the C programmer. Thus for example, the
IDL routine caGet()
returns, by default,
a value which has the native data type
and element count of the process variable. This is not true of the
corresponding C routine ezcaGet()
, which requires the user to
specify the data type and number of elements to be returned.
void
, i.e.
neither data nor status.
Procedure and function names are not case sensitive. Of course, the names of
channel access process variables are case sensitive and
must be specified correctly.
Status = caGet(pvname, value, /string, max=max) Status = caGetControlLimits(pvname, low, high) Status = caGetGraphicLimits(pvname, low, high) Status = caGetPrecision(pvname, precision) Status = caGetStatus(pvname, timestamp, status, severity) Status = caGetUnits(pvname, units) Status = caGetEnumStrings(pvname, strings) Status = caGetCountAndType(pvname, count, type)
Status = caPut(pvname, value)
Timeout = caGetTimeout() caSetTimeout, timeout RetryCount = caGetRetryCount() caSetRetryCount, retrycount
caStartGroup stat = caEndGroup(status)
Status = caSetMonitor(pvname) Status = caClearMonitor(pvname) State = caCheckMonitor(pvname)
caDebug, state caTrace, state caError, err_string, /ON, /OFF, /PRINT, prefix=prefix
ezcaIDL.pro
.
caGet()
and
caPut()
wait for all required
channel access operations to complete before they return. This is often
convenient, but it is very inefficient if one wants to read/write a large
number of process variables. In this case it is much more efficient to submit a
group of channel access requests, and then wait for them all to complete.
ezcaIDL supports the concept of "synchronous groups" in the EZCA library. A synchronous group is started by calling
caStartGroupOnce a synchronous group is started, subsequent calls to routines like
caGet()
, caPut()
, etc.
simply queue a channel access operation, and do not actually
perform the channel access I/O. Calling
status = caEndGroup()ends a synchronous group. This causes all of the queued channel access calls to be issued and waits for them to complete.
There are two important restrictions which must be kept in mind when calling
any of the caGetxxx()
routines (e.g. caGet()
,
caGetUnits()
,
caGetControlLimits()
, etc.) from inside a synchronous group,
i.e. after calling
caStartGroup
and before calling caEndGroup()
.
caEndGroup()
.
The reason for this is that EZCA has
been passed the addresses of these variables as the locations to which the data
are to be copied when caEndGroup()
is called.
Thus, these locations must still
point to a valid memory location when caEndGroup()
is called.
If the output
variables are re-used then IDL's behavior is unpredictable, and bus
errors/access violations could occur.
In practice, fatal errors have not been
observed, but they are possible.
caGet()
to read strings,
the data type returned will be a byte
array, rather than a string. The reason has to do with the manner in which
IDL passes strings, which requires that EZCA actually be passed pointers to
byte arrays. When caGet()
is called outside of
a group it automatically
converts the byte array to a string before returning the value. However when
caGet()
is called inside of a
synchronous group it cannot perform this
conversion, since it cannot be done until after the data is read, which does
not occur until caEndGroup()
is called.
Thus, it is the user's responsibility
to convert the data from a byte array to a string after calling
caEndGroup()
.
This is done very simply with the string()
function.
caStartGroup status = caGet('test_mca1.VAL', mca_value) status = caGet('test_vme1.DESC', vme_desc) ; This is a string PV status = caEndGroup() vme_desc = string(vme_desc) ; Convert from byte array to stringThe following is an example of an invalid grouped operation.
caStartGroup status = caGet('test_mca1.VAL', mca_value) status = caGet('test_vme1.VAL', vme_value) mca_value=0 status = caEndGroup()Note that
mca_value
was redefined before calling
caEndGroup()
, so the previous location became
undefined. Do not do this!
ezcaIDL.pro
and
ezcaIDL.c
.
The EZCA routines will, by default, print brief diagnostic error messages when errors occur. These messages can be turned off by calling:
caError, /OFFA message describing the most recent error can be printed on stdout by calling:
caError, /PRINTA string describing the most recent error can be returned to the caller with:
caErrror, err_stringFor more information on error messages see the description of caError in the ezcaIDL Reference Guide
caTrace, 1This can be turned off by calling
caTrace, 0Even more detailed debugging information for the EZCA routines can be obtained by calling:
caDebug, 1This can be turned off by calling
caDebug, 0
There were several motivations for creating this new library for IDL:
ca_pend_io()
or ca_pend_event()
.
For example, in CaWave caget()
of a list of process variables
can only return
an array of doubles or an array of strings. It is not possible to return
other data types, and it is not possible to have an asynchronous get of a
process variable which is itself an array.
ezcaIDL uses the ezcaStartGroup
and
ezcaEndGroup
calls to allow any mix of caPut
and
caGet
calls, and for these
to include any mix of data types, including arrays.
All applications which use CaWave will need to be modified if they are to use ezcaIDL instead. However, the conversion is generally quite simple.
The major differences between ezcaIDL and CaWave include the following:
caGet()
on a scalar process
variable always returned a
double or a string. In ezcaIDL caGet()
returns,
by default, a value with
the native data type of the process variable.
caGet
can be forced to
return a string value with the /STRING
keyword switch.
caGet()
was a function which returned
the value as the function
return. In ezcaIDL caGet()
returns a status
code as the function return
value and returns the data value in a parameter. This change was
required in order to implement EZCA synchronous groups. It is also
viewed as desirable to return a status value on all operations which can
generate a channel access error, and to make the syntax of calling
caGet
and caPut
similar.
caGet()
. In
ezcaIDL caGet()
automatically reads the
monitor value for a process
variable if caSetMonitor()
has been
called for that process variable.
caGet()
and caPut()
.
caGet
returned an array of values, while
caPut
wrote an array of values.
This method was quite restrictive, since
the values were required to be either all scalar doubles or
all scalar strings.
In ezcaIDL the concept of synchronous groups is much less
restrictive, since it permits any mix of
calls to caGet
, caPut
and other
routines, and there is no restriction on the data types or number of
elements in the values for these calls.
caPendIO
and caPendEvent
directly. This
is not necessary or possible in ezcaIDL. The timeouts for channel access
operations are now set with
caSetTimeout
and caSetRetryCount
.
ezcaIDL.c
call_external()
or
PV-WAVE linknload()
to the form
required by EZCA. It directly implements some functions which are not
provided in the EZCA library.
These include ezcaIDLGetCountAndType()
and ezcaIDLGetEnumStrings()
.
This file is compiled and linked into a shareable
object file, typically ezcaIDL.so
on Unix and
ezcaIDL.EXE
on VMS.
ezcaIDL.pro
ezcaWidgets.pro
ezcaIDLGuide.html
ezcaIDLRef.html
ezcaIDL.pro
.
extensions/.../ezca base/.../libca base/.../libCom
.login
file will facilitate
the use of ezcaIDL.
# Define an IDL or PV-WAVE startup file to be executed when IDL or PV-WAVE are # started setenv IDL_STARTUP ~/idl_startup.pro setenv WAVE_STARTUP ~/wave_startup.pro # # Define the location of ezcaIDL.so or ezcaWave.so so these files can be located # no matter what the current default directory is. These need to be modified # according to where the .so files are placed on your system. setenv ezcaIDL_share /usr/local/epics/extensions/bin/sun4/ezcaIDL.so setenv ezcaWave_share /usr/local/epics/extensions/bin/sun4/ezcaWave.soIn the IDL or PV-WAVE startup files (
~/idl_startup.pro
or ~/wave_startup.pro
in
the preceeding example .login
file) add the following lines
; !QUIET=1 ; So things will compile without informational messages .RUN ezcaIDL ; For both IDL and PV-WAVE .RUN ezcaIDLWidgets ; For IDL widget users ;
LOGIN.COM
file will
facilitate the use of
ezcaIDL.
$! Define an IDL or PV-WAVE startup file to be executed when IDL or PV-WAVE $! are started $ DEFINE IDL_STARTUP SYS$LOGIN:idl_startup.pro $ DEFINE WAVE_STARTUP SYS$LOGIN:wave_startup.pro $! $! Define the location of ezcaIDL.EXE or ezcaWave.EXE so these files can be $! located no matter what the current default directory is. $! These need to be modified according to where the .EXE files are placed on $! your system. $ DEFINE ezcaIDL_EXE PUBLIC_DISK:[PUBLIC.EPICS.EXTENSIONS.BIN]ezcaIDL.EXE $ DEFINE ezcaIDL_SHARE ezcaIDL_EXE or $ DEFINE ezcaWave_EXE PUBLIC_DISK:[PUBLIC.EPICS.EXTENSIONS.BIN]ezcaWave.EXE $ DEFINE ezcaWave_SHARE ezcaWave_EXEIn the IDL or PV-WAVE startup files (
idl_startup.pro
or wave_startup.pro
in
the preceeding example login.com
file) add the following lines
; !QUIET=1 ; So things will compile without informational messages .RUN ezcaIDL ; For both IDL and PV-WAVE .RUN ezcaIDLWidgets ; For IDL widget users ;
; Create a sine wave array, write it to a waveform record, read it back again ; and plot it. IDL> name = "idl_test:wf1" IDL> status = caGetCountAndType(name, n, type) IDL> data = sin(dindgen(n) * 2 * !PI / (n-1)) IDL> status = caPut(name, data) IDL> status = caGet(name, readback) IDL> plot, readback ; Print the list of valid values for the .SCAN field of a record as strings, ; one per line. status = caGetEnumStrings('idl_test:ai1.SCAN', choices) for i=0, n_elements(choices)-1 do print, choices(i) ; Print out the next 10 values for a process variable which is changing by ; waiting for monitor events. ; pv = 'mlr_scanner' status = caSetMonitor(pv) ; Add a monitor on this pv status = caGet(pv, data) ; Read the value, which clears the monitor flag for i=1, 10 do begin count = 0 status = 0 while (caCheckMonitor(pv) ne 0) and (count le 100) do begin wait, .01 count = count + 1 ; Assumes new monitors come faster if (count eq 100) then status = -1 ; than 1 per second endwhile if (status ne 0) then begin print, 'Monitor wait failed for ', pv endif else begin status = caGet(pv, data) print, 'New value = ', data endelse endfor status = caClearMonitor(pv) ; The following is an example of a synchronous group operation ; It also shows how to handle strings in synchronous groups. caStartGroup status = caGet('test_mca1.VAL', mca_value) status = caGet('test_vme1.VAL', vme_value) status = caGet('test_vme1.DESC', vme_desc) ; This is a string PV status = caEndGroup() vme_desc = string(vme_desc) ; Convert from byte array to string
ezcaIDLWidgets.pro
contains 3 routines which
simplify the use of channel
access, and particularly channel access monitors, with the IDL widget toolkit.
caSetMonitor()
.
If this is the first time caWidgetSetMonitor
has been called
then it creates a
dummy (iconified) widget which runs a timer routine. The timer routine
periodically calls caCheckMonitor(name)
to determine whether a
channel access monitor has arrived for "name".
If a monitor has occurred then an
event will be sent to the widget whose ID is specified by "widget_id".
The event structure is as follows:
event = { id ; The widget ID which was passed to caWidgetSetMonitor top: ; The top level widget in this hierarchy handler: ; The widget handler routine name: ; The name of the process variable for which a monitor has ; occurred. }When the event is sent, the event handler routine for the specified widget will be called. Generally this routine look at the
event.id
field to determine that
this is a monitor event (rather than a mouse event). If the same event handler
can receive monitor events from more than one process variable, (because
caWidgetSetMonitor
was called for several process variables)
the event handler
will then look at the event.name
field to determine which
process variable generated the monitor event.
Typically the widget_id which is passed to caWidgetSetMonitor
should be the id
of a base widget. Base widgets cannot generate events due to
mouse clicks, etc. so the widget event handler routine can distinguish monitor
events from mouse events by looking at the widget.id
field.
This is the same
concept which is described in the IDL documentation for timer events, e.g.
widget_control, wid, timer=1.0
caWidgetSetMonitor
can be called for many
different process variable names and
widget_ids. The widgets do not need to belong to the same widget hierarchy.
Multiple widgets can monitor the same process variable, and the same widget can
be used to monitor several process variables. Internally
caWidgetSetMonitor
maintains a list of all monitored process variables, and which widget_id(s) are
to receive events from each process variable.
The "time" keyword to caWidgetSetMonitor
can be
used to control the time
interval between polling cycles. The default is 0.1 seconds.
This routine sounds complex, but in fact it is simple to use and greatly simplifies the use of channel access monitors with IDL widget, since without it each widget event routine would have to poll to detect the arrival of channel access monitors. The following is a simple example of the use of this routine:
pro example_event, event common example_common, pv_name, widget_ids ; This is the event handler routine, called whenever any type of event ; (monitor, mouse, timer) occurs. case event.id of widget_ids.monitor: begin ; Read the new value and display it status = caGet(event.name, value, /string) widget_control, widget_ids.value, set_value=value end widget_ids.exit: begin t=CaWidgetClearMonitor(pv_name, widget_ids.monitor) widget_control, event.top, /destroy end endcase end pro example, name common example_common, pv_name, widget_ids ; This is the main routine for the example. ; It is passed the name of a process variable to monitor. ; It creates a simple screen with a value field for the monitored process ; variable and an EXIT button widget_ids= { $ monitor: 0L, $ value: 0L, $ exit: 0L } base=widget_base(title="Example", /column) ; The base widget widget_ids.monitor=base ; The monitor widget id=base widget_ids.value=widget_text(base, xsize=20) ; Widget to display new value widget_ids.exit=widget_button(base, value="Exit") ; Exit button widget_control, base, /realize ; Display the widgets t=caWidgetSetMonitor(name, widget_ids.monitor); Call caWidgetSetMonitor pv_name = name ; Copy name to common xmanager, "example", base ; Start the program end
caWidgetSetMonitor
.
If there are no other
widgets monitoring this process variable then caClearMonitor
is called to
completely remove the channel access monitor on this name.
name
is the name of the process variable to be adjusted.
The font
keyword can be used to specify a font to use.
The min
and max
keywords can be used to specify the upper and lower limits
of the slider widget when adjusting numeric process variables.
The label
keyword can
be used to put a descriptive label at the top of the
widget.
The group
keyword can be
used to set the id of the parent widget. If the
widget specified by group
is deleted,
then the widget created by
CaWidgetAdjust
will also be deleted.