This chapter describes the PV API. It is intended for those who would like to add support for new message systems. It need not be read by those who want to write sequences using message systems that are already supported.
The PV (Process Variable) API was introduced at version 2.0 in order to hide the details of the underlying message system from the sequencer code. Previously, the sequencer code ( i.e. the modules implementing the sequencer run-time support, not the user-written sequences) called CA routines directly. Now it calls PV routines, which in turn call routines of the underlying message system. This allows new message systems to be supported without changing sequencer code.
Several EPICS tools support both CA and CDEV. They do so in ad hoc ways. For example, medm uses an MEDM_CDEV macro and has medmCA and medmCdev modules, whereas alh has an alCaCdev module that implements the same interface as the alCA module.
The PV API is an attempt at solving the same problem but in a way that is independent of the tool to which it is being applied. It should be possible to use the PV API (maybe with some backwards-compatible extensions) with medm , alh and other CA-based tools. Having done that, supporting another message system at the PV level automatically supports it for all the tools that use the PV API.
Doesn't this sound rather like the problem that CDEV is solving? In a way, but PV is a pragmatic solution to a specific problem. The PV API is very close in concept to the CA API and is designed to plug in to a CA-based tool with minimal disruption. Why not use the CA API and implement it for other message systems? That could have been done, but would have made the PV API dependent on the EPICS db_access.h definitions (currently it is dependent only on the EPICS OSI layer).
In any case, a new API was defined and the sequencer code was converted to use it.
The public interface is defined in the file pv.h , which defines various types such as pvStat , pvSevr , pvValue , pvConnFunc and pvEventFunc , then defines abstract pvSystem , pvVariable and pvCallback classes. Finally it defines a C API.
The file pv.cc implements generic methods (mostly constructors and destructors) and the C API.
Each supported message system XXX creates a pvXxx.h file that defines xxxSystem (extending pvSystem ) and xxxVariable (extending pvVariable ) classes, and a pvXxx.cc file that contains the implementations of xxxSystem and xxxVariable .
Currently-supported message systems are CA and a Keck-specific one called KTL. The CA layer is very thin ( pvCa.h is 104 lines and pvCa.cc is 818 lines; both these figures include comments).
The file pvNew.cc implements a newPvSystem function that takes a system name argument ( e.g. " ca "), calls the appropriate xxxSystem constructor, and returns it (as a pvSystem pointer). It would be good to change it to use dynamically-loaded libraries, in which case there would be no direct dependence of the pv library on any of the pvXxx libraries ( c.f. the way CDEV creates cdevService objects).
void event( void *obj, pvType type, int count, pvValue *val,
pvVariable *var = ( pvVariable * ) obj;
printf( "event: %s=%g\n", var->getName(), val->doubleVal[0] );
int main( int argc, char *argv[] ) {
const char *sysNam = ( argc > 1 ) ? argv[1] : "ca";
const char *varNam = ( argc > 2 ) ? argv[2] : "demo:voltage";
pvSystem *sys = newPvSystem( sysNam );
pvVariable *var = sys->newVariable( varNam );
void event( void *var, pvType type, int count, pvValue *val,
printf( "event: %s=%g\n", pvVarGetName( var ),
int main( int argc, char *argv[] ) {
const char *sysNam = ( argc > 1 ) ? argv[1] : "ca";
const char *varNam = ( argc > 2 ) ? argv[2] : "demo:voltage";
pvSysCreate( sysNam, 0, &sys );
pvVarCreate( sys, varNam, NULL, NULL, 0, &var );
We will look at the contents of pv.h (and pvAlarm.h ) in more detail and will specify the constraints that must be met by underlying message systems.
pv.h and pvAlarm.h define various types, described in the following sections.
The negative codes correspond to the few CA status codes that were used in the sequencer. The positive codes correspond to EPICS STAT values.
#define PV_SIMPLE(_type) ( (_type) <= pvTypeSTRING )
Only the types required by the sequencer are supported, namely simple and "time" types. The "error" type is used to indicate an error in a routine that returns a pvType as its result.
typedef char pvString[256]; /* use sizeof( pvString ) */
#define PV_VALPTR(_type,_value) \
( void * ) ( &_value->timeCharVal.value ) ) )
pvValue is equivalent to db_access_val and, like it, is not self-describing (remember, the idea is that the PV layer is a drop-in replacement for CA).
Obviously, the introduction of pvValue means that values must be converted between it and the message system's internal value representation. This is a performance hit but one that was deemed worthwhile given that there is currently no appropriate "neutral" (message system independent) value representation. Once the replacement for GDD is available, it will maybe be used in preference to pvValue .
typedef void (*pvConnFunc)( void *var, int connected );
typedef void (*pvEventFunc)( void *var, pvType type, int count,
pvValue *value, void *arg, pvStat status );
In both cases, the var argument is a pointer to the pvVariable that caused the event. It is passed as a void* so that the same function signature can be used for both C and C++. In C, it would be passed to one of the pvVarXxx routines; in C++ it would be cast to a pvVariable* .
pvConnFunc is used to notify the application that a control system variable has connected or disconnected
pvEventFunc is used to notify an application that a get or put has completed, or that a monitor has been delivered
pvSystem is an abstract class that must be extended by specific message systems. An application typically contains a single instance, created by newPvSystem as described in See Overview. There's nothing to stop an application having several instances, each corresponding to a different message system, but the sequencer doesn't do this. Also, there is no way to pend on events from a set of pvSystem s.
Refer to pv.h for explicit detail. The following sections describe various important aspects of the class.
The newVariable method creates a new pvVariable corresponding to the same message system as the calling pvSystem . It should be used in preference to the concrete xxxVariable constructors since it doesn't require knowledge of xxx !
The flush and pend methods correspond to ca_flush , ca_pend_io and ca_pend_event (the latter two are combined into a single pend method with an optional wait argument; wait=FALSE gives ca_pend_io behavior, i.e. exit when pending activity is complete, and wait=TRUE gives ca_pend_event behavior, i.e. wait until timer expires).
The lock and unlock methods take and give a (recursive) mutex that can be used to prevent more than one thread at a time from being within message system code. This is not necessary for thread-safe message systems such as CA.
A debug flag is supported (it's an optional argument to the constructor and to the newVariable method) and is used to report method entry, arguments and other information. Debug flags are used consistently throughout the entire PV layer.
A message system-specific status, a severity ( pvSevr ), a status ( pvStat ), and an error message, are maintained in member variables. The concrete implementations should use the provided accessor functions to maintain up-to-date values for them. The pvVariable class supports the same interface.
pvVariable is an abstract class that must be extended by specific message systems. It corresponds to a control system variable accessed via its message system. Each pvVariable object is associated with a pvSystem object that manages system-wide issues like locking and event handling.
Refer to pv.h for explicit detail. The following sections describe various important aspects of the class.
The constructor specifies the corresponding pvSystem , the variable name (which is copied), an optional connection function, an optional private pointer, and an optional debug flag (0 means to inherit it from the pvSystem ).
The constructor should initiate connection to the underlying control system variable and should arrange to call the connection function (if supplied) on each connect or disconnect.
Like CDEV, the PV API supports the following get methods:
pvStat get( pvType type, int count, pvValue *value );
pvStat getNoBlock( pvType type, int count, pvValue *value );
pvStat getCallback( pvType type, int count, pvEventFunc func,
Like CDEV, the PV API supports the following put methods:
pvStat put( pvType type, int count, pvValue *value );
pvStat putNoBlock( pvType type, int count, pvValue *value );
pvStat putCallback( pvType type, int count, pvValue *value,
pvEventFunc func, void *arg = NULL );
The PV API supports the following monitor methods:
pvStat monitorOn( pvType type, int count, pvEventFunc func,
void *arg = NULL, pvCallback **pCallback = NULL );
pvStat monitorOff( pvCallback *callback = NULL );
CDEV is an obvious message system to support. This section should provide the necessary information to support it or another message system. It includes an example of a partly functional file message system.
Note that file names in this section are assumed to be relative to the top of the sequencer source tree.
This section gives a check-list. See See Example for an example of each stage.
Edit src/pv/pvNew.cc according to existing conventions. Assume that the PVXXX pre-processor macro is defined if and only if support for XXX is to be compiled in. See See src/pv/pvNew.cc for an example.
By convention, the configure/RELEASE file defines the various PVXXX make macros. See See configure/RELEASE for an example.
By convention, XXX support should be compiled only if the PVXXX make macro is defined and set to TRUE . See See pv/src/Makefile for an example.
Edit application Makefile s to search the pvXxx library and any other libraries that it references. It is, unfortunately, necessary, to link applications against all message systems. This is because src/pv/pvNew.cc references them all. This problem will disappear if and when pvNew is changed to load pvXxx libraries dynamically by name. See See test/pv/Makefile for an example.
As an example, we consider a notional file message system with the following attributes:
The files pvFile.h and pvFile.cc can be found in the src/pv directory. They compile and run but do not implement full functionality (left as an exercise for the reader!).
Only some sections of the file are shown.
class fileSystem : public pvSystem {
virtual pvStat pend( double seconds = 0.0, int wait = FALSE );
virtual pvVariable *newVariable( const char *name,
pvConnFunc func = NULL, void *priv = NULL, int debug = 0 );
class fileVariable : public pvVariable {
fileVariable( fileSystem *system, const char *name, pvConnFunc
func = NULL, void *priv = NULL, int debug = 0 );
virtual pvStat get( pvType type, int count, pvValue *value );
virtual pvStat getNoBlock( pvType type, int count,
virtual pvStat getCallback( pvType type, int count, pvEventFunc
virtual pvStat put( pvType type, int count, pvValue *value );
virtual pvStat putNoBlock( pvType type, int count, pvValue
virtual pvStat putCallback( pvType type, int count, pvValue
*value, pvEventFunc func, void *arg = NULL );
virtual pvStat monitorOn( pvType type, int count, pvEventFunc
func, void *arg = NULL, pvCallback **pCallback = NULL );
virtual pvStat monitorOff( pvCallback *callback = NULL );
virtual int getConnected() const { return TRUE; }
virtual pvType getType() const { return pvTypeSTRING; }
virtual int getCount() const { return 1; }
fileSystem::fileSystem( int debug ) :
ifd_( fopen( "iFile", "r" ) ),
printf( "%8p: fileSystem::fileSystem( %d )\n", this, debug);
if ( ifd_ == NULL || ofd_ == NULL ) {
setError( -1, pvSevrERROR, pvStatERROR, "failed to open "
// initialize fd_set for select()
FD_SET( fileno( ifd_ ), &readfds_ );
pvStat fileVariable::get( pvType type, int count, pvValue *value )
printf( "%8p: fileVariable::get( %d, %d )\n", this, type,
printf( "would read %s\n", getName() );
strcpy( value->stringVal[0], "string" );
pvStat fileVariable::put( pvType type, int count, pvValue *value )
printf( "%8p: fileVariable::put( %d, %d )\n", this, type,
Edit this to support the file message system. Some parts of the file are omitted.
pvSystem *newPvSystem( const char *name, int debug ) {
if ( strcmp( name, "ca" ) == 0 )
if ( strcmp( name, "file" ) == 0 )
Edit this to support the file message system. Comment out these lines to disable use of message systems. Some parts of the file are omitted.
This includes rules for building the test programs of See A tour of the API. Only those rules are shown.