This product is available via an open source license
The ca provider is available as part of: pvAccessCPP
The current status is that, as far as I know, everything described in this document is implemented.
Since the very beginning of EPICS, channel access, both network protocol and API, provided client access to DBRecords. With EPICS V4 two new methods are available:
Using V4 terminology, data in a PVRecord consists of some combination of:
value alarm timeStamp display control valueAlarm
Different types of clients want different combinations of the above.
This document describes channel provider ca, which is implemented in pvAccessCPP. It uses the channel access network protocol to communicate with a server, i. e. the network protocol that has been used since 1990 to communicate with EPICS IOCs. But instead of the channel access API, the client uses the pvAccess API.
The following discussion only refers to the C++ implementation. However any changes in ca provider should also be make in the Java implementation.
pva is another way to connect to a DBRecord, but this only works if the IOC has qsrv installed. qsrv, which is provided with pva2pva , has full support for communicating with a DBRecord.
ca has the advantage that it does require any changes to an existing IOC.
channel access, which is provided with epics-base, defines and implements a protocol for client/server network communication.
epics-base provides both a client and a server implementation
This document only discusses the client API.
For details see:
EPICS Channel Access 4.13.1 Reference Manual
channel access allows a client to get, put, and monitor monitor data from a server. The data is defined by various DBD types.
The following, in epics-base/include, are the main include files that show the channel access API:
cadef.h db_access.h
The client requests data via one of the DBR types. For example
DBR_STS_DOUBLE returns a double status structure (dbr_sts_double) where struct dbr_sts_double{ dbr_short_t status; /* status of value */ dbr_short_t severity; /* severity of alarm */ dbr_long_t RISC_pad; /* RISC alignment */ dbr_double_t value; /* current value */ };
The server converts data between the native type of the field being accessed and the DBR type.
ca is a pvAccess Channel Provider that uses channel access to connect a client to a server.
With ca, the client data appears as pvData objects, i. e. ca converts the data provided by channel access to/from pvData
Thus a pvAccess client can communicate with an existing V3 EPICS IOC without making any changes to existing IOCs.
For an overview of pvData and pvAccess see: EPICS V4 Developer's Guide
ca requests data from the server with a DBR type that matches the native type. See the next section for more details.
All conversion to/from other types must be done by the client.
The primary goal of provider ca is to allow a client to use the pvAccess API instead of the channel access API.
The channel access API allows the client to pick any DBR type it wants and if the catype, which is the type for the DBRecord, does not match the DBR type the server converts between the types.
With ca the client uses pvData types that match the catype. Thus all conversions will be done on the client side.
Other than having the server perform conversion, ca attempts to let the client do everthing the client can now do with the channel access API.
ca implements the following pvAccess methods : getField, channelGet, channelPut and monitor.
For channelPut the only field that can be accessed is value. A client can issue puts with and without a callback from the server. The default is no callback. If createChannelPut has the option record[block=true] then a put callback used.
All of the other pvAccess methods provide access to fields value, alarm and timeStamp.
Depending on the type associated with the value field the following fields may also be available: display, control , and valueAlarm.
Thus a client can make requests like:
pvget -p ca -r "value,alarm,timeStamp,display,control,valueAlarm" names ...
ca will create a structure that has the fields requested but only for fields that are supported by the server.
Lets discuss the various fields.
This can be a scalar, scalarArray, or an enumerated structure.
For a scalar or scalarArray the ScalarType is one of the following: pvString, pvByte, pvShort, pvInt, pvFloat, or pvDouble.
Note that channel access does not support unsigned integers or 64 bit integers. But field request options, described in the next section, allow a client to receive data as pvUByte, pvUShort, pvUInt, pvLong, or pvULong.
A enumerated structure is created if the native type is DBR_ENUM.
Some examples are:
pvget -p ca -r value -i DBRlongout DBRlongout structure int value 0 mrk> pvget -p ca -r value -i DBRdoubleout DBRdoubleout structure double value 0 mrk> pvget -p ca -r value -i DBRshortArray DBRshortArray structure short[] value [] mrk> pvget -p ca -r value -i DBRstringArray DBRstringArray structure string[] value [aa,bb,cc] mrk> pvget -p ca -r value -i DBRmbbo01 DBRmbbo01 epics:nt/NTEnum:1.0 enum_t value int index 1 string[] choices [zero,one,two,three,four,five,six,seven,eight,nine,ten,eleven,twelve,thirteen,fourteen,fifteen] mrk>
Each of these is one of the property structures defined in pvData.
mrk> pvget -p ca -r alarm -i DBRdoubleout DBRdoubleout structure alarm_t alarm int severity 2 int status 3 string message HIHI mrk> pvget -p ca -r timeStamp -i DBRdoubleout DBRdoubleout structure time_t timeStamp long secondsPastEpoch 1529923341 int nanoseconds 314916189 int userTag 0 mrk> pvget -p ca -r display -i DBRdoubleout DBRdoubleout structure display_t display double limitLow -10 double limitHigh 10 string description string format F8.2 string units volts mrk> pvget -p ca -r control -i DBRdoubleout DBRdoubleout structure control_t control double limitLow -1e+29 double limitHigh 1e+29 double minStep 0 mrk> pvget -p ca -r valueAlarm -i DBRdoubleout DBRdoubleout structure valueAlarm_t valueAlarm boolean active false double lowAlarmLimit -8 double lowWarningLimit -6 double highWarningLimit 6 double highAlarmLimit 8 int lowAlarmSeverity 0 int lowWarningSeverity 0 int highWarningSeverity 0 int highAlarmSeverity 0
Each create method of class Channel, in pvAccess, has an argument PVStructure pvRequest, i. e. it is an argument for createChannelGet, createChannelPut, etc.
pvDataCPP/include/pv/createRequest.h provides a method:
PVStructurePtr createRequest(string const & request);Thus given a request string it creates a pvRequest structure. In examples, this document uses the request string. It is assumed that client code calls the above method to create a pvRequest structure.
A pvRequest structure allows a client to select:
The following provides a fuller description pvRequest
For ca a valid request string for creating a pvRequest is one of the following:
At present the following record options are supported:
This is used to define the queueSize for monitors. The default is:
record[queueSize=2]
A larger size can be specified.
This is used to specify if a process request should block until the record completes processing. This is only valid for channelPut.
record[block=true]
If not specified the default is false.
An event mask can be specified for createMonitor.
record[DBE=value]value can be any combination of DBE_VALUE|DBE_ALARM|DBE_ARCHIVE|DBE_PROPERTY
channel access has no support for unsigned integers. The following allows a client to ask that ca ask convert from signed to unsigned.
field(value[dbtype=DBF_UCHAR])Only valid for DBR_CHAR.
field(value[dbtype=DBF_USHORT])Only valid for DBR_LONG.
field(value[dbtype=DBF_ULONG])Only valid for DBR_DOUBLE.
field(value[dbtype=DBF_INT64])Only valid for DBR_DOUBLE.
field(value[dbtype=DBF_UINT64])Only valid for DBR_DOUBLE.
These can be used to access strings longer than MAX_STRING_SIZE.
field(value[pvtype=pvString])NOTES:
This section provides guidelines for code developers that use ca to connect a client to a server.
This includes plugins for things like MEDM, EDM, caqtDM, etc. But also means any code that use ca: pvget, pvput, pvaClientCPP, exampleCPP/exampleClient, etc.
If any combination of display, control, and valueAlarm are monitored then it is suggested that two monitors are created. One has a request:
record[DBE=DBE_PROPERTY]field(display,control,valueAlarm)The other has a request:
value,alarm,timeStamp
The above two examples show the complete set of fields that can be requested. An application can uses a subset of the fields shown.
An alarm handler might make create a monitor with a request like:
record[DBE=DBE_ALARM]field(alarm)
An archiver might make create a monitor with a request like:
record[DBE=DBE_ARCHIVE]field(value,alarm,timeStamp)
exampleCPP Has example client and server code for using pvData and pvAccess.
In particular look at: exampleClient for example client code.
The examples all use pvaClientCPP It has examples:
Each example has a create method that accepts the following arguments:
The above examples work with any channel provider. The rest of this section is for clients using provider ca.
NOTE: If pvaClientCPP is used then it automatically calls CAClientFactory::start.
The channel access reference manual describes channel context: CA Client Contexts and Application Specific Auxiliary Threads
A brief summary of channel context is that only the thread that calls CAClientFactory::start() and associated auxillary threads can call ca_xxx functions.
The public access to ca is:
class epicsShareClass CAClientFactory { public: static void start(); ... };
Any code that uses ca must call CAClientFactory::start() before making any pvAccess client requests. ca_context_create is called for the thread that calls CAClientFactory::start(). If the client creates auxillary threads the make pvAccess client requests then the auxillary threads will automatically become a ca auxilary thread.
Deadlock in ca_clear_subscription() describes a problem with monitor callbacks. A test was created that shows that the same problem can occur with a combination of rapid get, put and monitor events.
In order to prevent this problem ca creates the following threads: channelConnectThread, getDoneThread, putDoneThread, and monitorEventThread. All client callbacks are made via one of these threads. For example a call to the requester's monitorEvent method is made from the monitorEventThread.
This section provides background material that helps understand design decisions for ca.
rawtype DBF DBR pvData ScalarType char[MAX_STRING_SIZE] DBF_STRING DBR_STRING pvString epicsInt8 DBF_CHAR DBR_CHAR pvByte epicsUint8 DBF_UCHAR DBR_CHAR pvByte epicsInt16 DBF_SHORT DBR_SHORT pvShort epicsUInt16 DBF_USHORT DBR_LONG pvInt epicsInt32 DBF_LONG DBR_LONG pvInt epicsUInt32 DBF_ULONG DBR_DOUBLE pvDouble epicsInt64 DBF_INT64 DBR_DOUBLE pvDouble epicsUInt64 DBF_UINT64 DBR_DOUBLE pvDouble float DBF_FLOAT DBR_FLOAT pvFloat double DBF_DOUBLE DBR_DOUBLE pvDouble epicsUInt16 DBF_ENUM DBR_ENUM enum structure epicsUInt16 DBF_MENU DBR_ENUM enum structureNotes:
recordtype(ao) { include "dbCommon.dbd" field(VAL,DBF_DOUBLE) { ... } field(OVAL,DBF_DOUBLE) { ... } ... many more fieldsIn addition each record type has a associated set of support code defined in recSup.h
/* record support entry table */ struct typed_rset { long number; /* number of support routines */ long (*report)(void *precord); long (*init)(); long (*init_record)(struct dbCommon *precord, int pass); long (*process)(struct dbCommon *precord); long (*special)(struct dbAddr *paddr, int after); long (*get_value)(void); /* DEPRECATED set to NULL */ long (*cvt_dbaddr)(struct dbAddr *paddr); long (*get_array_info)(struct dbAddr *paddr, long *no_elements, long *offset); long (*put_array_info)(struct dbAddr *paddr, long nNew); long (*get_units)(struct dbAddr *paddr, char *units); long (*get_precision)(const struct dbAddr *paddr, long *precision); long (*get_enum_str)(const struct dbAddr *paddr, char *pbuffer); long (*get_enum_strs)(const struct dbAddr *paddr, struct dbr_enumStrs *p); long (*put_enum_str)(const struct dbAddr *paddr, const char *pbuffer); long (*get_graphic_double)(struct dbAddr *paddr, struct dbr_grDouble *p); long (*get_control_double)(struct dbAddr *paddr, struct dbr_ctrlDouble *p); long (*get_alarm_double)(struct dbAddr *paddr, struct dbr_alDouble *p); };The methods that support accessing data from the record include:
cvt_dbaddr Implemented by record types that determine VAL type at record initialization *array_info Implemented by array record types get_units Implemented by numeric record types get_precision Implemented by float and double record types *_enum_* Implemented by enumerated record types get_graphic_double NOTE Always returns limits as double get_control_double NOTE Always returns limits as double get_alarm_double NOTE Always returns limits as doubleEach of these methods is optional, i. e. record support for a particular record type only implements methods that make sense for the record type. For example the enum methods are only implemented by records that have the definition:
... field(VAL,DBF_ENUM) { ... } ...
rawtype DBR char[MAX_STRING_SIZE] DBR_STRING epicsInt8 DBR_CHAR epicsInt16 DBR_SHORT epicsInt32 DBR_LONG float DBF_FLOAT double DBF_DOUBLE epicsUInt16 DBR_ENUMIn addition to the DBR basic types the following DBR types provide additional data:
DBR one of the types above. DBR_STATUS_* adds status and severity to DBR. DBR_TIME_* adds epicsTimeStamp to DBR_STATUS. DBR_GR_* adds display limits to DBR_STATUS. NOTE: no epicsTimeStamp DBR_CTRL_ adds control limits to DBR_GR. NOTE: no epicsTimeStamp DBR_CTRL_ENUM This is a special case.NOTES:
DBR_STS_DOUBLE returns a double status structure (dbr_sts_double) where struct dbr_sts_double{ dbr_short_t status; /* status of value */ dbr_short_t severity; /* severity of alarm */ dbr_long_t RISC_pad; /* RISC alignment */ dbr_double_t value; /* current value */ }; DBR_TIME_DOUBLE returns a double time structure (dbr_time_double) where struct dbr_time_double{ dbr_short_t status; /* status of value */ dbr_short_t severity; /* severity of alarm */ epicsTimeStamp stamp; /* time stamp */ dbr_long_t RISC_pad; /* RISC alignment */ dbr_double_t value; /* current value */ }; DBR_GR_SHORT returns a graphic short structure (dbr_gr_short) where struct dbr_gr_short{ dbr_short_t status; /* status of value */ dbr_short_t severity; /* severity of alarm */ char units[MAX_UNITS_SIZE]; /* units of value */ dbr_short_t upper_disp_limit; /* upper limit of graph */ dbr_short_t lower_disp_limit; /* lower limit of graph */ dbr_short_t upper_alarm_limit; dbr_short_t upper_warning_limit; dbr_short_t lower_warning_limit; dbr_short_t lower_alarm_limit; dbr_short_t value; /* current value */ }; DBR_GR_DOUBLE returns a graphic double structure (dbr_gr_double) where struct dbr_gr_double{ dbr_short_t status; /* status of value */ dbr_short_t severity; /* severity of alarm */ dbr_short_t precision; /* number of decimal places */ dbr_short_t RISC_pad0; /* RISC alignment */ char units[MAX_UNITS_SIZE]; /* units of value */ dbr_double_t upper_disp_limit; /* upper limit of graph */ dbr_double_t lower_disp_limit; /* lower limit of graph */ dbr_double_t upper_alarm_limit; dbr_double_t upper_warning_limit; dbr_double_t lower_warning_limit; dbr_double_t lower_alarm_limit; dbr_double_t value; /* current value */ }; DBR_CTRL_DOUBLE returns a control double structure (dbr_ctrl_double) where struct dbr_ctrl_double{ dbr_short_t status; /* status of value */ dbr_short_t severity; /* severity of alarm */ dbr_short_t precision; /* number of decimal places */ dbr_short_t RISC_pad0; /* RISC alignment */ char units[MAX_UNITS_SIZE]; /* units of value */ dbr_double_t upper_disp_limit; /* upper limit of graph */ dbr_double_t lower_disp_limit; /* lower limit of graph */ dbr_double_t upper_alarm_limit; dbr_double_t upper_warning_limit; dbr_double_t lower_warning_limit; dbr_double_t lower_alarm_limit; dbr_double_t upper_ctrl_limit; /* upper control limit */ dbr_double_t lower_ctrl_limit; /* lower control limit */ dbr_double_t value; /* current value */ }; DBR_CTRL_ENUM returns a control enum structure (dbr_ctrl_enum) where struct dbr_ctrl_enum{ dbr_short_t status; /* status of value */ dbr_short_t severity; /* severity of alarm */ dbr_short_t no_str; /* number of strings */ char strs[MAX_ENUM_STATES][MAX_ENUM_STRING_SIZE]; /* state strings */ dbr_enum_t value; /* current value */ };
pvAccessCPP/src/ca has files dbdToPv.h and dbdToPv.cpp. This is the code that converts between DBR data and pvData.
This code must decide which of the many DBR_* types to use.
There is a static method:
static DbdToPvPtr create( CAChannelPtr const & caChannel, epics::pvData::PVStructurePtr const & pvRequest, IOType ioType); // one of getIO, putIO, and monitorIO
When this is called the first thing is to determine which fields are requested by the client. This is from the set value, alarm, timeStamp. display, control , and valueAlarm.
If the channel type is DBR_ENUM a one time ca_array_get_callback(DBR_GR_ENUM... request is issued to get the choices for the enumerated value.
Depending or which fields are still valid, the DBR type is obtained via:
Each field of a record, that can be accessed by clients, has one of the following types:
A client can access the value field of a record and some of other fields. The value field can be read and modified. What a client can do with other fields depends on the dbd definition for the field.
For the value field clients can receive but not modify the following type of information:
DBRstatus // all in dbCommon; no client support for getting directly epicsUInt16 status // available to client via other means epicsUInt16 severity // available to client via other means epicsUInt16 acks // available to client only via channel.ACKS epicsUInt16 ackt // available to client only via channel.ACKT DBRunits // rset get_units char units[DB_UNITS_SIZE] DBRprecision // rset get_precision epicsInt32 precision DBRtime // in dbCommon; no client support for getting directly epicsTimeStamp time // available to client via other means DBRenumStrs // rset get_enum_strs epicsUInt32 no_str char strs[DB_MAX_CHOICES][MAX_STRING_SIZE]; /* string values */ DBRgrLong //NOTE NO rset method epicsInt32 upper_disp_limit epicsInt32 lower_disp_limit DBRgrDouble // rset get_graphic_double epicsFloat64 upper_disp_limit epicsFloat64 lower_disp_limit DBRctrlLong //NOTE NO rset method epicsInt32 upper_ctrl_limit epicsInt32 lower_ctrl_limit DBRctrlDouble // rset get_control_double epicsFloat64 upper_ctrl_limit epicsFloat64 lower_ctrl_limit DBRalLong //NOTE NO rset method epicsInt32 upper_alarm_limit epicsInt32 upper_warning_limit epicsInt32 lower_warning_limit epicsInt32 lower_alarm_limit DBRalDouble // rset get_alarm_double epicsFloat64 upper_alarm_limit epicsFloat64 upper_warning_limit epicsFloat64 lower_warning_limit epicsFloat64 lower_alarm_limit
The following are the rset methods related to client data access:
long (*get_array_info)(struct dbAddr *paddr, long *no_elements, long *offset); long (*put_array_info)(struct dbAddr *paddr, long nNew); long (*get_units)(struct dbAddr *paddr, char *units); long (*get_precision)(const struct dbAddr *paddr, long *precision); long (*get_enum_str)(const struct dbAddr *paddr, char *pbuffer); long (*get_enum_strs)(const struct dbAddr *paddr, struct dbr_enumStrs *p); long (*put_enum_str)(const struct dbAddr *paddr, const char *pbuffer); long (*get_graphic_double)(struct dbAddr *paddr, struct dbr_grDouble *p); long (*get_control_double)(struct dbAddr *paddr, struct dbr_ctrlDouble *p); long (*get_alarm_double)(struct dbAddr *paddr, struct dbr_alDouble *p);
Each field of a channel that can be accessed by a client has a chtype which is one of the following types:
For put channel access only allows the client to access the above types of data.
For get and monitor the client can access the following additional data:
DBR one of the types above. DBR_STATUS_* adds status and severity to DBR. DBR_TIME_* adds epicsTimeStamp to DBR_STATUS. DBR_GR_* adds display and alarm limits to DBR_STATUS. DBR_CTRL_* adds control limits to DBR_GR. DBR_CTRL_ENUM Same info as DBRenumStrs from rset get_enum_strsNOTES:
In addition channel access defines a monitor event mask:
A few comments about the monitor event mask:
When caqtDm connects to a channel it creates a monitor specifying DBR_CTRL_... and mask DBE_PROPERTY. When the callback for that request occurs, It then creates a monitor specifying DBR_STS_... and an empty mask. Thus for rare events it asks for a lot and for frequent events it asks only for value and alarm status and severity.
Code in epics-base converts between channel access data and the data in a DBRecord. Since these do not match some problems exist.
channel access does not support 64 bit integers. Record access makes these fields appear as a double. Thus the field appears to channel access as a DBR_DOUBLE. This works for both scalar and array. But once the 64 bit value exceeds the number of mantissa bits in a double it will loose lower order bits.
channel access does not support unsigned integers
Most DBR_STRING fields are limited to MAX_STRING_SIZE. If a field is larger then, unless something is done, a channel access client can only access MAX_STRING_SIZE characters.
Clients have two ways to get longer strings but each relies on the client and server using the same convention. The two ways are:
This section shows the relationship between pvData standard fields and DBR data.
enum_t value int index 0 string[]
alarm_t alarm int severity int status string message // not in any DBR types
time_t timeStamp long secondsPastEpoch // seconds since 0000 Jan 1, 1970 UTC int nanoseconds int userTag // not in epicsTimeStamp
Note that epicsTimeStamp is:
typedef struct epicsTimeStamp { epicsUInt32 secPastEpoch; /* seconds since 0000 Jan 1, 1990 UTC */ epicsUInt32 nsec; /* nanoseconds within second */ } epicsTimeStamp;
control_t control double limitLow double limitHigh double minStep // not in any DBR type
display_t display double limitLow double limitHigh string description // not in any DBR type; field DESC is in dbCommon string format // DBR types only define precision string units // provided by DBRunits
format is a problem but what to do?
DBRecord has field PREC.
valueAlarm_t valueAlarm boolean active // not in any DBR type double lowAlarmLimit double lowWarningLimit double highWarningLimit double highAlarmLimit int lowAlarmSeverity // not in any DBR type int lowWarningSeverity // not in any DBR type int highWarningSeverity // not in any DBR type int highAlarmSeverity // not in any DBR type double hysteresis // not in any DBR type // recordTypes that support valueAlarm // should have field HYST
The above is OK. But there is a version for each numeric type. control and display only have double for limit fields. Also record support only has a method for DBRalDouble.
ca only uses valueAlarm with limits as double.
Is this a problem?
Is it handled by a client issuing gets and puts to fields ACKS and ACKT?
ca always sets the bit for field value for each get and each monitor event. This seems like the correct semantics since DBRecords already have support for not raising monitors if a scalar value field does not change by a significant amount. It could be quite expensive to check for changes to array value fields.
Both epics-base and pvdata have support code for alarms. The definition of status is different between them. ca converts from the epics-base version to the pvdata version.
Beginning with the 3.15 releases of epics-base channel filters are supported.
Using the channel access client API, the client requests plugins by appending to the channel name.
An example requesting the array plugin is:
caget test:channel test:channel.[3:5] test:channel.[3:2:-3] test:channel 10 0 1 2 3 4 5 6 7 8 9 test:channel.[3:5] 3 3 4 5 test:channel.[3:2:-3] 3 3 5 7
This works using the channel access client API and also for channel providers qsrv and ca.
Thus ca does not support any plugin options.