20 KiB
pvAccessCPP: ca provider
2018.07.09
Editors:
- Marty Kraimer
This is a description of channel provider ca that is implemented as part of pvAccessCPP.
It uses the channel access network protocol to communicate with a server, i. e. the network protocol that has been used to communicate with EPICS IOCs since 1990.
Provider 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. The only advantage of ca is that it does require any changes to an existing IOC.
The following are discussed.
Introduction
The primary purpose of the ca provider is to access DBRecords in an EPICS IOC via the channel access network protocol but to use pvData objects for the client.
Each DBRecord instance has a record name that must be unique in the local area network. A client can access any public field of a DBRecord; Each DBRecord instance has a record name that is unique with in the local area network A channel name is a recordname.fieldName. If the fieldname is not specified then .VAL is assumed
example database
The following:
mrk> pwd
/home/epicsv4/masterCPP/pvAccessCPP/testCa
mrk> softIoc -d testCaProvider.db
Starts an EPICS IOC that is used for all examples in this document.
examples
mrk> caget -d DBR_TIME_FLOAT DBRdoubleout
DBRdoubleout
Native data type: DBF_DOUBLE
Request type: DBR_TIME_FLOAT
Element count: 1
Value: 1
Timestamp: 2018-06-21 06:23:07.939894
Status: NO_ALARM
Severity: NO_ALARM
mrk> pvget -p ca -r "value,alarm,timeStamp" -i DBRdoubleout
DBRdoubleout
structure
double value 1
alarm_t alarm
int severity 0
int status 0
string message
time_t timeStamp
long secondsPastEpoch 1529576587
int nanoseconds 939894210
int userTag 0
mrk> pvget -p ca -r "value,alarm,timeStamp" DBRdoubleout
DBRdoubleout
structure
double value 1
alarm_t alarm NO_ALARM NO_STATUS <no message>
time_t timeStamp 2018-06-21T06:23:07.940 0
mrk> pvget -p ca -r value -i DBRdoubleout.SCAN
DBRdoubleout.SCAN
epics:nt/NTEnum:1.0
enum_t value
int index 0
string[] choices [Passive,Event,I/O Intr,10 second,5 second,2 second,1 second,.5 second,.2 second,.1 second]
Overview of Channel Access
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.
Overview of ca provider
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:
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.
Client API
ca implements the following pvAccess methods : getField, channelGet, channelPut and monitor.
For channelPut the only field that can be accessed is value. For channelPut 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 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.
- For puts only value is supported.
- For gets and monitors every channel supports value, alarm, and timeStamp;
- If any of display,control, or valueAlarm are requested then timeStamp is NOT available.
Lets discuss the various fields.
value
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.
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 DBRmbbin
DBRmbbin
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>
alarm,timeStamp,display,control, and valueAlarm
Each of these is one of the property structures defined in pvData.
Examples
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
DBD to pvData
Type Conversion
Three type systems are involved in accessing data in a DBRecord and converting it to/from pvData:
- DBF The type system used for DBRecords.
- DBR The type system used by channel access.
- pvData
The following gives a summary of the conversions between the type systems:
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 no support
epicsUInt64 DBF_UINT64 no support
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 structure
Notes:
- Both DBF_CHAR and DBF_UCHAR go to DBR_CHAR. This is ambigous.
- DBF_USHORT promoted to DBR_LONG
- DBF_ULONG promoted to DBR_DOUBLE
- qsrv provides full access to all DBF types, but the IOC must have qsrv installed.
Accessing data in a DBRecord
An IOC database is a memory resident database of DBRecord instances.
Each DBRecord is an instance of one of an extensible set of record types. Each record type has an associated dbd definition which defines a set of fields for each record instance.
For example an aoRecord.dbd has the definition:
recordtype(ao) {
include "dbCommon.dbd"
field(VAL,DBF_DOUBLE) {
...
}
field(OVAL,DBF_DOUBLE) {
...
}
... many more fields
In 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 double
Each 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) {
...
}
...
Channel Access Data
A client can access any public field of a DBRecord; Each DBRecord instance has a record name that is unique within the local area network.
A channel name is a recordname.fieldName.
If the fieldname is not specified then .VAL is assumed and the record support methods shown above can also be used to get additional data from the record.
Any field that is accessable by client code must have a vald DBF_ type.
A client gets/puts data via a DBR_* request.
The basic DBR types are:
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_ENUM
In 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:
- status, severity, and epicsTimeStamp are the same for each DBR type.
- limits have the same types as the correspondng DBR type.
- server converts limits from double to the DBR type.
- GR and CTRL have precision only for DBR_FLOAT and DBR_DOUBLE
Some examples:
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 */
};
PVData for a DBRrecord via ca provider
pvAccessCPP/src/ca has files dbdToPv.h and dbdToPv.cpp. This is the code that converts between DBD 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 ioType is putIO only value is valid.
- If the channel type is DBR_ENUM then display, control , and valueAlarm are ignored.
- If the channel is an array then control , and valueAlarm are ignored.
- If the channel type is DBR_STRING then display, control , and valueAlarm are ignored.
- If any of display, control , and valueAlarm are still allowed then timeStamp is ignored, because the DBR type selected will not return the timeStamp.
If ths 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
- If any of display, control ,or valueAlarm is valid then dbf_type_to_DBR_CTRL(caValueType) .
- else If alarm or timeStamp is valid then dbf_type_to_DBR_TIME(caValueType) .
- else dbf_type_to_DBR(caValueType)
Where caValueType is one of DBR_STRING, DBR_SHORT, DBR_FLOAT, DBR_ENUM, DBR_CHAR, DBR_LONG, DBR_DOUBLE.
If display is still valid then the following call is made:
string name(caChannel->getChannelName() + ".DESC");
int result = ca_create_channel(name.c_str(),
...
When the channel connects a get is issued to get the value for display.description.
Developing plugins for ca provider
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.
The channel access reference manual describes channel context:
CA Client Contexts and Application Specific Auxiliary Threads
A brief summary of channel context is:
- 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:
/** @brief start provider ca
*
*/
static void start();
/** @brief get the ca_client_context
*
* This can be called by an application specific auxiliary thread.
* See ca documentation. Not for casual use.
*/
static ca_client_context * get_ca_client_context();
/** @brief stop provider ca
*
* This does nothing since epicsAtExit is used to destroy the instance.
*/
static void stop();
};
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()
Shows 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: getEventThread, putEventThread, 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.
Notes
- These threads do not call ca_attach_context.
- No ca_xxx function should be called from the requester's callback method.