Files
pvAccess/caProvider.md
2018-07-14 11:46:26 -04:00

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:

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.

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.