Several existing tools, including medm and alh, have both CA and CDEV versions. Each tool has its own method of handling the two systems.
An alternative approach is to define an API which is independent of both the tool and the underlying message systems and to write tools to use it rather than explicitly calling routines of a specific message system. The result is that new message systems can be added without changing tools (at most re-linking them and providing some way for them to select the message system).
Such an API has been defined as part of the port of the EPICS sequencer to Unix. It is referred to as the "PV" (process variable) API. It has been implemented for CA (pvCa) and for KTL (pvKtl), with a CDEV implementation to follow, and the sequencer's CA calls have been changed to PV calls. The API has been designed to have a close fit to CA and it should always be easy to convert applications from CA to PV.
The standard "demo" sequence (three state sets) runs unchanged under Unix using both pvCa and pvKtl. The same sequence runs under VxWorks using pvCa (there is no VxWorks version of KTL).
typedef enum {
pvStatOK
= 0,
pvStatERROR = -1,
pvStatDISCONN = -2,
pvStatTIMEOUT = -3
} pvStat;
typedef enum {
pvSevrERROR = -1,
pvSevrNONE =
0,
pvSevrMINOR = 1,
pvSevrMAJOR = 2,
pvSevrINVALID = 3
} pvSevr;
typedef enum {
pvTypeERROR
= -1,
pvTypeCHAR
= 0,
pvTypeSHORT
= 1,
pvTypeLONG
= 2,
pvTypeFLOAT
= 3,
pvTypeDOUBLE
= 4,
pvTypeSTRING
= 5,
pvTypeTIME_CHAR =
6,
pvTypeTIME_SHORT = 7,
pvTypeTIME_LONG =
8,
pvTypeTIME_FLOAT = 9,
pvTypeTIME_DOUBLE = 10,
pvTypeTIME_STRING = 11
} pvType;
and also a pvValue union which supports the pvType types. It then defines virtual base classes pvSystem (each underlying message system sub-classes this and returns an instance of the sub-classed object) and pvVariable (message systems also sub-class this; there is one instance per process variable).
/*
* Connect (connect/disconnect and event (get,
put and monitor) functions
*/
typedef void (*pvConnFunc)( void *var, int connected
);
typedef void (*pvEventFunc)( void *var, pvType type,
int count,
pvValue *value, void *arg, pvStat status );
/*
* System
*
* This is somewhat analogous to a cdevSystem
object (CA has no equivalent
* and must therefore use ca_static, ca_import()
and ca_import_cancel())
*/
class pvSystem {
public:
pvSystem( int debug = 0 );
virtual ~pvSystem();
inline pvSystem &getSystem() { return *this; }
virtual pvStat flush() = 0;
virtual pvStat pend( double seconds
= 0.0, int wait = FALSE ) = 0;
virtual pvVariable *newVariable(
char *name, pvConnFunc func = NULL,
int debug = 0 ) = 0;
void lock();
void unlock();
inline int getMagic() { return
magic_; }
inline void setDebug( int debug
) { debug_ = debug; }
inline int getDebug() { return
debug_; }
inline void setError( int status,
pvSevr sevr, pvStat stat,
const char *mess )
{ status_
= status; sevr_ = sevr; stat_ = stat; mess_ = mess; }
inline int getStatus() { return
status_; }
inline pvSevr getSevr() { return
sevr_; }
inline pvStat getStat() { return
stat_; }
inline const char *getMess() {
return mess_; }
private:
int
magic_; /* magic number
(used for authentication) */
int
debug_; /* debugging level
(inherited by pvs) */
int
status_; /* message system-specific
status code */
pvSevr
sevr_; /* severity
*/
pvStat
stat_; /* status
*/
const char *mess_;
/* error message */
OSIsemBinary lock_;
/* prevents more than one thread in library */
};
////////////////////////////////////////////////////////////////////////////////
/*
* Process variable
*
* This is somewhat analogous to a cdevDevice
object (or a CA channel)
*/
class pvVariable {
public:
pvVariable( pvSystem &system,
char *name, pvConnFunc func = NULL,
int debug = 0 );
virtual ~pvVariable();
virtual pvStat get( pvType type,
int count, pvValue *value ) = 0;
virtual pvStat getNoBlock( pvType
type, int count, pvValue *value ) = 0;
virtual pvStat getCallback( pvType
type, int count,
pvEventFunc func, void *arg = NULL ) = 0;
virtual pvStat put( pvType type,
int count, pvValue *value ) = 0;
virtual pvStat putNoBlock( pvType
type, int count, pvValue *value ) = 0;
virtual pvStat putCallback( pvType
type, int count, pvValue *value,
pvEventFunc func, void *arg = NULL ) = 0;
virtual pvStat monitorOn( pvType
type, int count,
pvEventFunc func, void *arg = NULL,
pvCallback **pCallback = NULL ) = 0;
virtual pvStat monitorOff( pvCallback
*callback = NULL ) = 0;
virtual int getConnected() = 0;
virtual pvType getType() = 0;
virtual int getCount() = 0;
inline int getMagic() { return
magic_; }
inline void setDebug( int debug
) { debug_ = debug; }
inline int getDebug() { return
debug_; }
inline pvConnFunc getFunc() {
return func_; }
inline pvSystem &getSystem()
{ return system_; }
inline char *getName() { return
name_; }
inline void setPrivate( void *priv
) { private_ = priv; }
inline void *getPrivate() { return
private_; }
private:
int
magic_; /* magic number
(used for authentication) */
int
debug_; /* debugging level
(inherited from system) */
pvConnFunc func_;
/* connection state change function */
pvSystem &system_;
/* associated system */
char
*name_; /* variable name
*/
void
*private_; /* client's private data */
};
pv.h also defines a pvCallback class, which is used for communicating information to callback handlers. Finally, it defines the C interface, of which a few routines are:
pvStat pvSysCreate( char *name, void **pSys );
pvStat pvSysDestroy( void *sys );
pvStat pvSysFlush( void *sys );
pvStat pvSysPend( void *sys, double seconds, int
wait );
pvStat pvSysLock( void *sys );
pvStat pvSysUnlock( void *sys );
pvStat pvVarCreate( void *sys, char *name, pvConnFunc
func, void **pVar );
pvStat pvVarDestroy( void *var );
pvStat pvVarGet( void *var, pvType type, int count,
pvValue *value );
pvStat pvVarGetNoBlock( void *var, pvType type, int
count, pvValue *value );
pvStat pvVarGetCallback( void *var, pvType type,
int count,
pvEventFunc func, void *arg );
The file pv.cc implements constructors and destructors for pvSystem, pvVariable and pvCallback. It also implements the pvSystem lock() and unlock() methods (which can be used by message systems if they need them). Finally, it implements the C interface. This does not have to be implemented for the individual message systems.
Here are some examples.
/* invoke CA function and send error details to system
object */
#define INVOKE(_function) \
do { \
int _status
= _function; \
getSystem().setError(
_status, sevrFromCA( _status ), \
statFromCA( _status ), ca_message( _status ) ); \
} while ( FALSE )
caSystem::caSystem( int debug ) :
pvSystem( debug )
{
if ( getDebug() > 0 )
printf(
"%8p: caSystem::caSystem( %d )\n", this, debug );
INVOKE( ca_task_initialize() );
}
pvStat caVariable::monitorOn( pvType type, int count,
pvEventFunc func,
void *arg, pvCallback **pCallback )
{
if ( getDebug() > 0 )
printf(
"%8p: caVariable::monitorOn( %d, %d )\n",
this, type, count );
pvCallback *callback = new pvCallback(
*this, type, count, func, arg,
getDebug() );
evid id = NULL;
INVOKE( ca_add_masked_array_event(
typeToCA( type ), count, chid_,
monitorHandler, callback, 0.0, 0.0, 0.0,
&id, DBE_VALUE|DBE_ALARM ) );
callback->setPrivate( id );
if ( pCallback != NULL )
*pCallback
= callback;
return getSystem().getStat();
}
static void monitorHandler( struct event_handler_args
args )
{
pvCallback &callback = * (
pvCallback * ) args.usr;
pvEventFunc func = callback.getFunc();
pvVariable &variable = callback.getVariable();
pvType type = callback.getType();
int count = callback.getCount();
void *arg = callback.getArg();
pvValue *value = new pvValue[count];
// ### larger than needed
copyFromCA( args.type, args.count,
( union db_access_val * ) args.dbr,
value );
// ### should assert args.type
is equiv to type and args.count is count
( *func ) ( ( void * ) &variable,
type, count, value, arg,
statFromCA( args.status ) );
delete [] value;
}
KTL is not of general interest to the EPICS community, so only one code example is given. Note that KTL is not thread-safe and can activate shareable libraries which may not be thread-safe, so a lock is taken around most KTL calls. For example
/* lock / unlock shorthands */
#define LOCK getSystem().lock()
#define UNLOCK getSystem().unlock()
pvStat ktlVariable::put( pvType type, int count, pvValue
*value )
{
if ( getDebug() > 0 )
printf(
"%8p: ktlVariable::put( %d, %d )\n", this, type, count );
KTL_POLYMORPH ktlValue;
INVOKE( copyToKTL( type, count,
value, type_, count_, &ktlValue ) );
if ( getSystem().getStat() ==
pvStatOK ) {
LOCK;
INVOKE(
ktl_write( getHandle(), KTL_WAIT, keyName_, NULL, &ktlValue,
NULL ) );
UNLOCK;
freeKTL(
type_, &ktlValue );
}
return getSystem().getStat();
}