This product is made available subject to acceptance of the EPICS open source license.
pvIOCCPP is the C++ implementation of pvIOC,
which is one part of the set of related products in the EPICS
V4 control system programming environment:
relatedDocumentsV4.html
This is the 16-Nov-2012 version of the C++ implementation of pvIOC. This release implements only the following:
The following is a list of unresolved issues for pvIOCCPP:
pvIOCCPP is one of a set of related projects. This project uses projects pvDataCPP and pvAccessCPP. It is assumed that the reader is familar with these projects.
At the present time the only parts of pvIOCCP that are implemented are:
These are described in this document. Everything else that appears in this project is work in progress.
A complete Java implementation of pvIOC (pvIOCJava) is available but it can not be used inside a V3 IOC.
Doxygen documentation is available at doxygenDoc
V3Channel provides access to V3 records. It can be used by other code in the same IOC or by pvAccess network clients via remote pvAccess. It implements channelProcess, channelGet, channelPut, channelArray, and Monitor. It does not implement channelPutGet or channelRPC since, at least for now, these do not apply to V3 records.
It also provides the ability to get and put a set of V3 records. Only data from field VAL is allowed and all the V3 records must have the same scalar numeric type. The data appears as a scalarArray. The records can be processed. If all V3 records are in the same lockSet then each get or put is an atomic operation, i. e. dbScanLock is called only once. If the records are not in the same lockSet each record is individually locked.
Because V3 records have a flat record structure the top level structure the pvAccess client sees also has a "flat" structure. The exception is that alarm, timeStamp, display, control, and valueAlarm appear as the pvData property structures. For example an ai record will appear as:
mrk> pvget -r "field()" double01
double01
uri:ev4:nt/2012/pwd:NTScalar
double value 0
alarm_t alarm
int severity 3
int status 0
string message UDF
time_t timeStamp
long secondsPastEpoch 631152000
int nanoSeconds 0
int userTag 0
display_t display
double limitLow 0
double limitHigh 10
string description
string format %f
string units Counts
control_t control
double limitLow -0.1
double limitHigh 9.9
double minStep 0
valueAlarm_t valueAlarm
boolean active false
double lowAlarmLimit 2
double lowWarningLimit 4
double highWarningLimit 6
double highAlarmLimit 8
int lowAlarmSeverity 0
int lowWarningSeverity 0
int highWarningSeverity 0
int highAlarmSeverity 0
double hystersis 0
mrk>
The basic idea is to map what V3 records provide into a PVStruructure that has the same information. For example if a client makes the following request:
pvget -r "field()" quadruple:BField
The result is:
scalar_t
double value 0
alarm_t alarm
int severity 3
int status 0
string message UDF
timeStamp_t timeStamp
long secondsPastEpoch 631152000
int nanoSeconds 0
int userTag 0
display_t display
double limitLow 0
double limitHigh 10
string description
string format %f
string units tesla
control_t control
double limitLow 0
double limitHigh 10
double minStep 0
valueAlarm_t valueAlarm
boolean active false
double lowAlarmLimit 2
double lowWarningLimit 4
double highWarningLimit 6
double highAlarmLimit 8
int lowAlarmSeverity 0
int lowWarningSeverity 0
int highWarningSeverity 0
int highAlarmSeverity 0
double hystersis 0
The channelName can be either just a record name or a "recordName.XXX" where XXX is the V3 field name, e. g. "SCAN".
The following are examples:
mrk> pvput -r "record[process=true]putField(syncputb,syncputc)" syncputa 3 1 2 3
Old :
uri:ev4:nt/2012/pwd:NTScalarArray
double[] value [0,0,0]
[ChannelPutRequesterImpl] message(atomic true, info)
New :
uri:ev4:nt/2012/pwd:NTScalarArray
double[] value [1,2,3]
mrk> pvget -r "record[process=true]getField(syncgetb,syncgetc)" syncgeta
[ChannelGetRequesterImpl] message(atomic true, info)
syncgeta
uri:ev4:nt/2012/pwd:NTScalarArray
double[] value [2,1,1]
Only get and put are supported. Only record names, i.e. no field name, can be given. All the V3 records must have the same scalar numeric scalar type. The channelName (in the above syncputa or syncgeta) is the first of the set of records desired. The putField for get or the getField for get is a list of the other records.
If process is requested then the semantics of V3 record processing must be kept in mind. Processing a record may cause the processing of linked records. If a record is an asynchronous record then the record may not complete processing until after the get or put completes. If the record is already active a new process request will not be made.
If an application wants a client to be able to do an atomic get or put to a set of record then a forward link record can be used to force the records into the same lockSet. For example:
record(calc, "syncgeta"){...}
record(calc, "syncgetb"){...}
record(calc, "syncgetc"){...}
record(fanout,"syncgetfanout")
{
field(LNK1,"syncgeta")
field(LNK2,"syncgetb")
field(LNK3,"syncgetc")
}
NOTE: pvIOCCP has a iocBoot directory structure like any V3 application. To see an example do the following:
In one window:
mrk> pwd /home/mrk/hg/pvIOCCPP/iocBoot/simpleV3Channel mrk>../../bin/linux-x86_64/simpleV3Channel st.cmd
In another window:
mrk> pvget -r "field()" TESTDOUBLE
pvIOCCPP runs in a regular V3 IOC as defined by EPICS base. It uses the base build system and is built as an EPICS application. To use V3Channel do the following:
PVBASE=/home/mrk/hg
PVDATA=${PVBASE}/pvDataCPP
PVACCESS=${PVBASE}/pvAccessCPP
PVIOC=${PVBASE}/pvIOCCPP
include "base.dbd" include "v3Channel.dbd"
startV3Channel
Both iocCore and pvIOC are based on the concept of a database of memory resident "smart" records. A record is called smart because code is attached to the record and is triggered by asking the record to process.
CAV3 (channel access version 3), which is a component of iocCore, provides network access to iocCore records. pvAccess provides network access to pvIOC records. Both access a record via the channelName which is just the name of the record and optionally the name of a field in the record. The record name must be unique within the local area network.
This section provides an overview of:
Each of these is discused first for iocCore and then for pvAccess/pvIOC.
For CAV3 a channelName consists of a "recordName.fieldName" The default fieldName is "VAL". The field data has one of the following types:
In addition a field can have some set of the following properties.
CAV3 provides I/O functions that falls into the following classes:
CAV3 provides no way for the client to influence record processing. Puts can cause processing but database access decides if a put causes a record to process.
For pvAccess a channelName consists of just the recordName. How the client specifies what data and options are desired are discussed below. For now just realize that each data item passed between client and server is a via an identical top level pvStructure on each side.
A brief description of a pvStructure is that it is a structure that has a set of PVFields where each field has one of the following types:
Although pvAccess itself does not get involved, pvData defines a set of "standard" PVStructures and assocated code called properties.
Note: pvData and pvAccess are not directly involved with normative types (NT). They merely make it possible to create NT structures.
The currently defined property structures are:
pvAccess provides the following:
When a client connects to a record the client receives an interface Channel. The interface has a create method for each of the above, i.e. createChannelProcess, etc. Each of these create methods has an argument "PVStructure pvRequest". The pvRequest structure is passed from the client to the server. pvAccess itself does not care what is in pvRequest; it is just a top level pvStructure. But a set of conventions exist for communication with a PVIOC. A pvAccess server should follow the same conventions unless it has special requirements.
There is a "convenience" service that, given a string, creates a pvRequest. A simple example is:
pvRequest = createRequest("field(alarm,timeStamp,value)");
The details are provided in pvAccessJava.html. The brief description is that the pvRequest allows the client to specify the following:
pvRequest = createRequest("record[process=true]field(alarm,timeStamp,value)");
This asks that the record be processed.
There are two standard record options:
A record is a flat structure, i.e. it is a structure that has a set of fields each of which has a name. Each field is one of the following types:
DBF_STRING, DBF_CHAR, DBF_UCHAR, DBF_SHORT, DBF_USHORT, DBF_LONG, DBF_ULONG, DBF_FLOAT, DBF_DOUBLE, DBF_ENUM, DBF_MENU, DBF_DEVICE, DBF_INLINK, DBF_OUTLINK, DBF_FWDLINK, DBF_NOACCESS
DBF_CHAR, ... ,DBF_DOUBLE are the numeric types. The other types are:
A record instance has a record type. Each record type has a fixed set of fields. Each has a common set of 43 fields and a set of record type specific fields. For example ai adds 39 fields and mbbi adds 65 fields. Most of these fields are not of interest to clients. The field of most interest is VAL. Many other fields are properties of the value field. Normally client code does not directly access property fields but gets their values via chnnelAccess. Many fields are private and can not be accessed via channelAccess.
EPICS base provides a set of record types, e. g. ai, ao, bi, bo, etc. It also provides facilities for defining additional record types. Each record type has associated code that is accessed via a rset (Record Support Entry Table):
typedef struct rset { /* record support entry table */
long number; /*number of support routines */
RECSUPFUN report; /*print report */
RECSUPFUN init; /*init support */
RECSUPFUN init_record; /*init record */
RECSUPFUN process; /*process record */
RECSUPFUN special; /*special processing */
RECSUPFUN get_value; /*get value field */
RECSUPFUN cvt_dbaddr; /*cvt dbAddr */
RECSUPFUN get_array_info;
RECSUPFUN put_array_info;
RECSUPFUN get_units;
RECSUPFUN get_precision;
RECSUPFUN get_enum_str; /*get string from enum item*/
RECSUPFUN get_enum_strs; /*get all enum strings */
RECSUPFUN put_enum_str; /*put string from enum item*/
RECSUPFUN get_graphic_double;
RECSUPFUN get_control_double;
RECSUPFUN get_alarm_double;
}rset;
These method are called by dbAccess, which is major component of iocCore. Method process is called to process the record. The details are determined by the record suport. The methods get_value, ... ,get_alarm_double are all related to the field named VAL. get_array_info and put_array_info apply if the value field is an array. get_enum_str ,..., put_enum_str apply if value is an enumerated field. The other get_xxx methods get properties of the value field.
Three fields from dbCommon releted to record processing are SCAN,FLNK, and DTYP. Many record types have either a field INPUT or OUTPUT.
Because processing a record can cause linked records to also be processed, the concept of scan set exists. Before a record is processed a function dbScanLock is called and when it completes processing dbScanUnlock is called. All records linked together directly or indirectly are placed in the same lock set. When dbScanLock is called the entire set, not just the specified record, is locked. This prevents two different tasks from simultaneously modifying records in the same lock set.
Device support can be either synchronous or asynchronous. A synchronous record is a record where device support can complete without wait for I/O. Asynchronous support is support that needs to wait. Asynchonous processing consists of two two phases: process start and process completion. Between the two phases the record is unlocked.
NOTE: currently record and record processing is only impelemented by pvIOCJava.
A PVRecord has a top level PVStructure. Thus the data layout is just PVData. As mentioned above pvData defines standard structures and support for enumerated, alarm, timeStamp, display, control, and valueAlarm.
pvIOC has the the same processing features as V3IOC. The main difference are:
The interface for support is:
interface Support {
void initialize();
void start(AfterStart afterStart);
void stop();
void uninitialize();
void process(SupportProcessRequester supportProcessRequester);
}
Although record types do not exist there are standards for field layout so that the equivalent of the V3IOC ai, ao, etc records can be created.
Support is provided that implements database and channelAccess links.
The status of interoperability between V3 and pvIOC/pvAccess is:
From the point of view of a pvAccess client.
A CAV3 client has access to a channel that is a PVRecord only if the record is on a pvIOCJava.
What is currently implemented by pvIOCCPP has almost non of the features a pvIOC is expected to provide. The documentation for pvIOCJava descibes what a full implementation provides.
Other then service and ezchannelRPC, which should be moved to pvAccessCPP, the only things pvIOCCPP currently provides are v3Channel, channelProviderLocal, and multiChannel.
v3Channel, which is a channelProvider for V3 records, is quite usedful and should remain a feature.
channelProviderLocal provides a framework for a channelProvider with the name local. It is a very weak version of pvIOCJava implements. It provides a pvAccess context and allows code to register channelProvider instances. BUT each instance must itself implement ChannelProvider and Channel. To fully implement Channel take a lot of code unless pvIOC provides help.
At present the only Channel implementations in pvIOCCPP are v3Channel and multiChannel.
v3Channel implements a pvAccess server for accessing V3 records. It implements everything that makes sense for V3 records.
multiChannel implements Channel and ChannelGet. Each instance provides a channel that holds data from a set of channels obtained from another channelProvider. But multiChannel is very limited. It currently only implements channelGet, only returns the complete set of data that the instance is configured to collect, and acts like every data value changed on every get request.
What is required is a much more. The following discusses a phased implementation to a full implementation of what a pvIOC should provide:
The minimum features will allow a full implementation of Channel but will require code for each type of functionality desired. In addition there will only be a single support for a record instance.
With the minimum features a complete Channel implementation for multiChannel is possible.
The minimum set of features is:
This phase adds the ability to optionally add support to any field of a record. This phase includes the set of basic support that pvIOCJava implements. This phase still requires code to create each record and register it with channelProvider.
This phase requires:
This provides access to all the support provided by asynDriver which is huge.
This provides a database and on-line add and remove records. It still requires code to create record instances.
This provides the ability to create records without writing code.
This is a replacement for the asynManger component of asynDriver. See the implementation in pvIOCJava for details.
NOTE: Most readers can skip this section.
This section is only of interest to someone who wants to understand the internals of v3Channel. Because v3Channel transfers data between pvData and V3 records it is complex. To understand it requires a knowledge of the internals of the V3 database and of pvData.
The implementation consists of the following header files:
Associated with each header file is a corresponding implementation file which has the same file name as the header but with an extension of ".cpp". The remainder of this section gives a brief description of each of these software components with an emphasis on how data is converted between pvData and data in V3 records.
In epics V3 there are two interfaces for accessing data:
This defines two interfaces:
V3ChannelProvider is defined as:
class V3ChannelProvider :
public epics::pvAccess::ChannelBaseProvider
{
public:
POINTER_DEFINITIONS(V3ChannelProvider);
static V3ChannelProviderPtr getV3ChannelProvider();
virtual ~V3ChannelProvider();
virtual ChannelFind::shared_pointer channelFind(
String const &channelName,
ChannelFindRequester::shared_pointer const & channelFindRequester);
virtual Channel::shared_pointer createChannel(
String const &channelName,
ChannelRequester::shared_pointer const &channelRequester,
short priority,
String const &address);
private:
}
where
V3Channel is defined as:
class V3Channel :
public virtual epics::pvAccess::ChannelBase
{
public:
POINTER_DEFINITIONS(V3Channel);
V3Channel(
gChannelBaseProvider::shared_pointer const & channelProvider,
gChannelRequester::shared_pointer const & requester,
gString const & name,
std::auto_ptr addr
);
virtual ~V3Channel();
void init();
virtual void getField(
gGetFieldRequester::shared_pointer const &requester,
gString const &subField);
virtual gChannelProcess::shared_pointer createChannelProcess(
gChannelProcessRequester::shared_pointer const &channelProcessRequester,
gPVStructure::shared_pointer const &pvRequest);
virtual gChannelGet::shared_pointer createChannelGet(
gChannelGetRequester::shared_pointer const &channelGetRequester,
gPVStructure::shared_pointer const &pvRequest);
virtual gChannelPut::shared_pointer createChannelPut(
gChannelPutRequester::shared_pointer const &channelPutRequester,
gPVStructure::shared_pointer const &pvRequest);
virtual gMonitor::shared_pointer createMonitor(
gMonitorRequester::shared_pointer const &monitorRequester,
gPVStructure::shared_pointer const &pvRequest);
virtual gChannelArray::shared_pointer createChannelArray(
gChannelArrayRequester::shared_pointer const &channelArrayRequester,
gPVStructure::shared_pointer const &pvRequest);
virtual void printInfo();
virtual void printInfo(StringBuilder out);
private:
...
}
where
This does most of the work related to transfering data between a PVStructure and a V3 record. All methods are static and are called by V3ChannelGet, V3ChannelPut, etc. Each first calls getProperty which returns a mask that is passed to the other methods. The mask bits show a combination of what the requester requested and characteristics of the V3 field being accessed. The mask has the following bits:
V3Util is defined as:
class V3Util {
public:
POINTER_DEFINITIONS(V3Util);
virtual ~V3Util() {}
static V3UtilPtr getV3Util();
// client request bits
int processBit; // is processing requested
int shareArrayBit; // share V3array instead of copy
int timeStampBit; // get timeStamp;
int alarmBit; // get alarm
int displayBit; // get display info
int controlBit; // get control info
int valueAlarmBit; // get value alarm info
// V3 data characteristics
int scalarValueBit; // value is a scalar
int arrayValueBit; // value is an array
int enumValueBit; // value is an enum
int noAccessBit; // fields can not be accessed
int noModBit; // fields can not be modified
int dbPutBit; // Must call dbPutField
int isLinkBit; // field is a DBF_XXLINK field
int getProperties(
gRequester::shared_pointer const &requester,
gPVStructure::shared_pointer const &pvRequest,
DbAddr &dbAddr,
bool processDefault);
gPVStructurePtr createPVStructure(
gRequester::shared_pointer const &requester,
int mask,DbAddr &dbAddr);
void getPropertyData(
gRequester::shared_pointer const &requester,
int mask,DbAddr &dbAddr,
gPVStructurePtr const &pvStructure);
gStatus get(
gRequester::shared_pointer const &requester,
int mask,DbAddr &dbAddr,
gPVStructurePtr const &pvStructure,
gBitSet::shared_pointer const &bitSet,
CAV3Data *caV3Data);
gStatus put(
gRequester::shared_pointer const &requester,
int mask,DbAddr &dbAddr,
gPVFieldPtr const &pvField);
gStatus putField(
gRequester::shared_pointer const &requester,
int mask,DbAddr &dbAddr,
gPVFieldPtr const &pvField);
gScalarType getScalarType(
gRequester::shared_pointer const &requester,
DbAddr &dbAddr);
private:
...
}
where
This creates special implementations of PVByteArray, ... , PVStringArray that access the corresponding array types in the V3 record. Except for PVStringArray it provides the ability to share data instead of making copies. It gets and puts array lengths via the V3 record support entry table methods. Note that it has speciaized methods for all of get, put, serialize, and deserialize that are not provided by PVDataCreate.
V3Array is defined as:
class V3ValueArrayCreate {
public:
POINTER_DEFINITIONS(V3ValueArrayCreate);
gPVByteArrayPtr createByteArray(
gPVStructurePtr const & parent,
gScalarArrayConstPtr const & scalar,
DbAddr &dbAddr,bool shareData);
gPVUByteArrayPtr createUByteArray(
gPVStructurePtr const & parent,
gScalarArrayConstPtr const & scalar,
DbAddr &dbAddr,bool shareData);
gPVShortArrayPtr createShortArray(
gPVStructurePtr const & parent,
gScalarArrayConstPtr const & scalar,
DbAddr &dbAddr,bool shareData);
gPVUShortArrayPtr createUShortArray(
gPVStructurePtr const & parent,
gScalarArrayConstPtr const & scalar,
DbAddr &dbAddr,bool shareData);
gPVIntArrayPtr createIntArray(
gPVStructurePtr const & parent,
gScalarArrayConstPtr const & scalar,
DbAddr &dbAddr,bool shareData);
gPVUIntArrayPtr createUIntArray(
gPVStructurePtr const & parent,
gScalarArrayConstPtr const & scalar,
DbAddr &dbAddr,bool shareData);
gPVFloatArrayPtr createFloatArray(
gPVStructurePtr const & parent,
gScalarArrayConstPtr const & scalar,
DbAddr &dbAddr,bool shareData);
gPVDoubleArrayPtr createDoubleArray(
gPVStructurePtr const & parent,
gScalarArrayConstPtr const & scalar,
DbAddr &dbAddr,bool shareData);
// Note that V3StringArray can not share
gPVStringArrayPtr createStringArray(
gPVStructurePtr const & parent,
gScalarArrayConstPtr const & scalar,
DbAddr &dbAddr);
};
typedef std::tr1::shared_ptr V3ValueArrayCreatePtr;
extern V3ValueArrayCreatePtr getV3ValueArrayCreate();
where
In V3 dbEvent is what implements db_post_event, which is what is called when a field value changes in a V3 record. But dbEvent just passes this to V3CA. Thus the only way to trap V3 monitors is via a call to ca_create_subscription. The implementation takes only the following from the CAV3 event callback: scalar value, alarm status and sererity, and the timeStamp. All other data, e.g. array values, are taken from the record as a result of the CAV3 event callback.
V3CAMonitor provides an enum that describes the data type of a V3 field as seen by pvData.
enum V3Type {
v3Enum,
v3Byte,
v3UByte,
v3Short,
v3UShort,
v3Int,
v3UInt,
v3Float,
v3Double,
v3String
};
where
Next a C structure is defined in which data from CAV3 is put. Each time CAV3 calls the event callback the CAV3 data is copied to this structure.
struct CAV3Data {
CAV3Data();
~CAV3Data();
/* The following have new values after each data event*/
union { //only used for scalar values
int8 byteValue;
uint8 ubyteValue;
uint16 ushortValue;
int16 shortValue;
int32 intValue;
uint32 uintValue;
float floatValue;
double doubleValue;
};
epicsTimeStamp timeStamp;
int sevr;
int stat;
const char * status;
};
where
Next is an interface that provides c++ callbacks that can be called by the C callbacks that CAV3 calls:
class CAV3MonitorRequester : public virtual Requester {
public:
POINTER_DEFINITIONS(CAV3MonitorRequester);
virtual ~CAV3MonitorRequester(){}
virtual void exceptionCallback(long status,long op) = 0;
virtual void connectionCallback() = 0;
virtual void accessRightsCallback() = 0;
virtual void eventCallback(const char *status) = 0;
where
Finally the interface for CAV3Monitor:
class CAV3Monitor : private epics::pvData::NoDefaultMethods {
public:
CAV3Monitor(
CAV3MonitorRequesterPtr const &requester,
epics::pvData::String const &pvName,
V3Type v3Type);
~CAV3Monitor();
CAV3Data & getData();
void connect();
void start();
void stop();
const char * getStatusString(long status);
bool hasReadAccess();
bool hasWriteAccess();
bool isConnected();
private:
...
}
where
CAV3Monitor calls CAV3 in order to monitor changes to the V3 records. In order to do this it must call ca_context_create(ca_enable_preemptive_callback). It must call this from the thread that wil make calls to CAV3. But it must also call ca_attach_context from any other threads associated with the thread that calls ca_context_create. The putpose of CAV3Context is to manage this interaction with CAV3.
:1,$s/For each thread that V3Channel uses and that makes a CreateMonitor CAV3Context keeps a reference count. This is decremented when CAV3Contect::release is called, which is done when a V3CAMonitor is deleted. Only when the thread that originally resulted in a CAV3Context exits is the CAV3Context deleted. Thus if a single client connects to several V3 records each instance of V3Channel and all ChannelGets, etc. for that channel share the same context.:q
CAV3Context is defined as:
class CAV3Context {
public:
POINTER_DEFINITIONS(CAV3Context);
~CAV3Context();
void release();
void stop();
void exception(String const &message);
void checkContext();
private:
...
};
where
There is a separate class for creating a context:
lass CAV3ContextCreate {
public:
static CAV3ContextPtr get(epics::pvData::RequesterPtr const & requester);
private:
...
}
where
This implements a channelProvider named "local" intended for use by code that wants to make data available to pvAccess clients. It has the following features:
To start localChannel provider the XXXInclude.dbd file used to build the application must include the following:
include "channelProviderLocal.dbd"
The st.cmd file that starts the IOC must have the statement:
startChannelProviderLocal
Code that provides data via a top level PVStructure has code like:
ChannelAccess::shared_pointer channelAccess = getChannelAccess();
ChannelProvider::shared_pointer channelProvider = channelAccess->getProvider("local");
ChannelProviderLocalPtr channelProviderLocal =
static_pointer_cast<ChannelProviderLocal>(channelProvider);
channelProviderLocal->registerProvider(channelName,provider);
The only method of interest to code that provides data via a top level PVStructure is:
ChannelProviderLocalPtr channelProviderLocal =
static_pointer_cast<ChannelProviderLocal>(channelProvider);
channelProviderLocal->registerProvider(channelName,provider);
To see an example of multiChannel start the following IOC:
mrk> pwd /home/mrk/hg/pvIOCCPP/iocBoot/testMultiValue mrk> ../../bin/linux-x86_64/testMultiValue st.cmd
A dbl command shows the following V3 records are present:
epics> dbl quadruple:BField quadruple:Current quadruple:POLYNOMIAL
Then in another window:
mrk> pwd /home/mrk/hg/pvIOCCPP/iocBoot/testMultiValue mrk> source clientTest
The file clientTest contains:
pvput quadruple:BField 1 pvput quadruple:Current 5 pvput quadruple:POLYNOMIAL 1,2,3,4,5 pvget quadruple
You should see:
Old :
scalar_t
double value 0
New :
scalar_t
double value 1
Old :
scalar_t
double value 0
New :
scalar_t
double value 5
Old :
structure
double[] value []
New :
structure
double[] value [1,2,3,4,5]
structure
alarm_t alarm
int severity 3
int status 0
string message UDF
timeStamp_t timeStamp
long secondsPastEpoch 1348773990
int nanoSeconds 368066848
int userTag 0
double bfield 1
double current 5
double[] conversionPolynomial [1,2,3,4,5]
MultiValue provides support for creating pvAccess channels that are made available via channelProviderLocal. Each channel holds data from the value fields of a set of channels provided by another channel provider. A primary use is the collect data from a set of V3 records in the same IOC.
Each channel has a top level structure that has an alarm and timeStamp field. In addition there is a field for each value field obtained from the other channel provider.
iocBoot/testMultiValue provides an example of how multiChannel is used.
The V3 database that is loaded is:
record(waveform, "quadruple:POLYNOMIAL")
{...}
record(ai, "quadruple:Current")
{...}
record(ai, "quadruple:BField")
{...}
The st.cmd file ends with:
createMultiValueChannel quadrupole.txt
The field quadrupole.txt contains:
channelValueProvider v3Channel
channelName quadrupole
channelValue
bfield quadrupole:BField
current quadrupole:Current
conversionPolynomial quadrupole:POLYNOMIAL
This says to create a pvAccess channel with channelName quadrupole. The channel provider for the data fields is v3Channel, i.e. the provider that accesses V3 records.
The command:
pvget -r "field()" quadrupole
will return the following structure:
structure
alarm_t alarm
int severity 3
int status 0
string message UDF
timeStamp_t timeStamp
long secondsPastEpoch 1348772838
int nanoSeconds 832012665
int userTag 0
double bfield 1
double current 5
double[] conversionPolynomial [1,2,3,4,5]
Look at pvIOCCPP/testApp/testMultiValue for an example of how to create an application that uses the multiChannel support. Look at pvIOCCPP/iocBoot/testMultiValue for an example of how to create a multiChannel instance. The st.cmd file ends with: createMultiValue quadruple.txt
This creates a multiChannel channel defined by file quadruple.txt which contains:
channelValueProvider v3Channel
channelName quadruple
channelValue
bfield quadruple:BField
current quadruple:Current
conversionPolynomial quadruple:POLYNOMIAL
Files createMultiValue.dbd and createMultiValueRegister.cpp are what registers and implements the iocshell command createMultiValue.
The syntax accepted by createMultiVaue is:
channelValueProvider channelProvider
channelName name
channelValue
fieldName channelName
...
This file describes the classes MultiValueChannelProvider, MultiValueChannel, and MultiValueChannelGet. It is only of interest it You want to understand the code. If so look at the code.
This is a wrapper for accessing a channel. It provides a synchronous interface for accessing a channel. It currently only provides support for channelGet. When EasyPVA is available it will not be needed.
This is support code for developing network based services that use pvAccess as the network protocol. In particular the server side of a pvAccess service.
Service provides support for the following:
Except for the RPC support, this code is only present because pvIOCCPP does not implement a pvIOC database, support, etc. When these are created the code should disappear.
class ServiceRPC
{
public:
POINTER_DEFINITIONS(ServiceRPC);
virtual void destroy() = 0;
virtual void request(
ChannelRPCRequester::shared_pointer const & channelRPCRequester,
PVStructure::shared_pointer const & pvArgument) = 0;
};
class ServiceChannelRPC :
public std::tr1::enable_shared_from_this<ServiceChannelRPC>
{
public:
POINTER_DEFINITIONS(ServiceChannelRPC);
ServiceChannelRPC(i
String const &channelName,
ServiceRPC::shared_pointer const & serviceRPC);
virtual ~ServiceChannelRPC();
virtual void destroy();
private:
...
};
ServiceRPC is the class the service must implement. It has the methods:
ServiceChannelRPC is a class that implements the server side of channelRPC. It will call ServiceRPC when a client issues a channelRPC request. It takes care of all other pvAccess requirements. It has the methods:
This is the header file for code that implements two classes: ChannelBaseProvider and ChannelBase. Each is discussed separately.
NOTE: A service that implements only ChannelRPC can ignore this section.
channelBaseProvider takes care of "lifetime" issues. It registers the provider, keeps a list of all channels the service provider creates and destroys all channels when the provider is destroyed.
class ChannelBaseProvider :
public ChannelProvider,
public std::tr1::enable_shared_from_this
{
public:
POINTER_DEFINITIONS(ChannelBaseProvider);
ChannelBaseProvider(
String const &providerName
);
virtual ~ChannelBaseProvider();
virtual String getProviderName();
virtual void destroy();
virtual ChannelFind::shared_pointer channelFind(
String const & channelName,
ChannelFindRequester::shared_pointer const & channelFindRequester) = 0;
virtual Channel::shared_pointer createChannel(
String const & channelName,
ChannelRequester::shared_pointer const &requester, short priority);
virtual Channel::shared_pointer createChannel(
String const & channelName,
ChannelRequester::shared_pointer const & channelRequester,
short priority,
String const & address) = 0;
// following called by derived class
void channelFound(
bool found,
ChannelFindRequester::shared_pointer const & requester);
void channelNotCreated(
ChannelRequester::shared_pointer const & requester);
void channelCreated(ChannelBase::shared_pointer const &channel);
void removeChannel(ChannelBase::shared_pointer const &channel);
void unregisterSelf();
void registerSelf();
protected:
...
}
The service must implement the following two ChannelProvider methods:
channelBaseProvider implements the following methods:
This implements all the Channel methods. This a service only needs the implement the methods it supports. For example a service may only implement ChannelGet and ChannelPut. In that case it only needs to implement the createChannelGet and createChannelPut methods of Channel as well as ChannelGet and ChannelPut.
class ChannelBase :
public virtual Channel,
public std::tr1::enable_shared_from_this<ChannelBase>
{
public:
POINTER_DEFINITIONS(ChannelBase);
ChannelBase(
ChannelProvider::shared_pointer const &channelProvider,
ChannelRequester::shared_pointer const & requester,
String const & channelName
);
virtual ~ChannelBase();
virtual void destroy();
virtual String getRequesterName();
virtual void message(
String const & message,
MessageType messageType);
virtual ChannelProvider::shared_pointer getProvider();
virtual String getRemoteAddress();
virtual Channel::ConnectionState getConnectionState();
virtual String getChannelName();
virtual ChannelRequester::shared_pointer getChannelRequester();
virtual bool isConnected();
virtual void getField(
GetFieldRequester::shared_pointer const &requester,
String const & subField);
virtual AccessRights getAccessRights(
PVField::shared_pointer const &pvField);
virtual ChannelProcess::shared_pointer createChannelProcess(
ChannelProcessRequester::shared_pointer const &requester,
PVStructure::shared_pointer const &pvRequest);
virtual ChannelGet::shared_pointer createChannelGet(
ChannelGetRequester::shared_pointer const &requester,
PVStructure::shared_pointer const &pvRequest);
virtual ChannelPut::shared_pointer createChannelPut(
ChannelPutRequester::shared_pointer const &requester,
PVStructure::shared_pointer const &pvRequest);
virtual ChannelPutGet::shared_pointer createChannelPutGet(
ChannelPutGetRequester::shared_pointer const &requester,
PVStructure::shared_pointer const &pvRequest);
virtual ChannelRPC::shared_pointer createChannelRPC(
ChannelRPCRequester::shared_pointer const &requester,
PVStructure::shared_pointer const &pvRequest);
virtual Monitor::shared_pointer createMonitor(
MonitorRequester::shared_pointer const &requester,
PVStructure::shared_pointer const &pvRequest);
virtual ChannelArray::shared_pointer createChannelArray(
ChannelArrayRequester::shared_pointer const &requester,
PVStructure::shared_pointer const &pvRequest);
virtual void printInfo();
virtual void printInfo(StringBuilder out);
// following called by derived classes
void addChannelProcess(ChannelProcess::shared_pointer const &);
void addChannelGet(ChannelGet::shared_pointer const &);
void addChannelPut(ChannelPut::shared_pointer const &);
void addChannelPutGet(ChannelPutGet::shared_pointer const &);
void addChannelMonitor(Monitor::shared_pointer const &);
void addChannelRPC(ChannelRPC::shared_pointer const &);
void addChannelArray(ChannelArray::shared_pointer const &);
void removeChannelProcess(ChannelProcess::shared_pointer const &);
void removeChannelGet(ChannelGet::shared_pointer const &);
void removeChannelPut(ChannelPut::shared_pointer const &);
void removeChannelPutGet(ChannelPutGet::shared_pointer const &);
void removeChannelMonitor(Monitor::shared_pointer const &);
void removeChannelRPC(ChannelRPC::shared_pointer const &);
void removeChannelArray(ChannelArray::shared_pointer const &);
};
The service only needs to implement the set of create methods that it supports. For each the service must also implement the associated ChannelXXX interfaces. The service may also want to implement the printInfo methods.
ChannelBase implements the following methods:
NOTE: A service that implements only ChannelRPC can ignore this section except for PVServiceChannelCTX.
This describes three classes: ServicePVTop, PVServiceProvider, and PVServiceChannelCTX. Each will be discussed separately.
This is the class that a service provider must implement.
class ServicePVTop
{
public:
POINTER_DEFINITIONS(ServicePVTop);
virtual ~ServicePVTop(){}
virtual String getName() = 0;
virtual ChannelBase::shared_pointer createChannel(
ChannelRequester::shared_pointer const &requester,
std::tr1::shared_ptr<PVServiceProvider> const &provider) = 0;
virtual void destroy() = 0;
};
where
This is a complete implementation of ChannelProvider. There is a single instance. The code that uses it must implement interface ServicePVTop. The code must also create a top level PVStructure, which is what the pvAccess client will access. PVServiceProvider has methods:
Each takes an argument ServicePVTop.
This is for use by services that want to run as a standalone service, i. e. not as part on a V3 IOC. It starts the remote server for pvAccess. See testApp/exampleServiceRPC/exampleServiceRPCMain.cpp for an example of how to start it.
This is pvAccess client code for making a ChannelRPC request. An effort is being started to develop an easy to use interface to pvAccess. When this facility is available something like ezchannelRPC will be part of it.
In order to make the example simpler there is also code that implement an easy to use interface to channelRPC.
class EZChannelRPC :
public ChannelRequester,
public ChannelRPCRequester,
public std::tr1::enable_shared_from_this<EZChannelRPC>
{
public:
POINTER_DEFINITIONS(EZChannelRPC);
EZChannelRPC(
epics::pvData::String const &channelName);
EZChannelRPC(
epics::pvData::String const &channelName,
epics::pvData::PVStructure::shared_pointer pvRequest);
virtual ~EZChannelRPC();
void destroy();
bool connect(double timeout);
void issueConnect();
bool waitConnect(double timeout);
epics::pvData::PVStructure::shared_pointer request(
epics::pvData::PVStructure::shared_pointer const & pvArgument,
bool lastRequest);
void issueRequest(
epics::pvData::PVStructure::shared_pointer const & pvArgument,
bool lastRequest);
epics::pvData::PVStructure::shared_pointer waitRequest();
epics::pvData::String getMessage();
...
}
where
Currently the only C++ support for services is support for channelRPC, i. e. support for services where the client and server communicate via pvAccess::channelRPC. In the near future support is planned for for channelPutGet.
There is an example service in testApp/exampleServiceRPC. This example can be run either as a main program or as part of V3 IOC using the v3Channel support described in the next section. To run it as a main program just execute the command:
bin/<arch>/exampleServiceRPC
To execute it as part of a V3 IOC do the following:
cd iocBoot/testV3Channel ../../bin/<arch>/testV3Channel st.cmd
Once the example service is running the example client can be executed, in another window, via the command:
bin/<arch>/exampleClient
The example is in testApp/exampleServiceRPC.
exampleServiceRPC.h contains:
class ExampleServiceRPC :
public virtual ServiceRPC,
public std::tr1::enable_shared_from_this<ExampleServiceRPC>
{
public:
POINTER_DEFINITIONS(ExampleServiceRPC);
ExampleServiceRPC();
virtual ~ExampleServiceRPC();
virtual void destroy();
virtual void request(
epics::pvAccess::ChannelRPCRequester::shared_pointer const & channelRPCRequester,
epics::pvData::PVStructure::shared_pointer const & pvArgument);
private:
ExampleServiceRPC::shared_pointer getPtrSelf()
{
return shared_from_this();
}
};
This just shows how the example extends ServiceRPC. All services should have a similar definition except for the name, i. e. replace ExampleService with some other name.
The implementation is in exampleServiceRPC.cpp:
ExampleServiceRPC::ExampleServiceRPC(){}
ExampleServiceRPC::~ExampleServiceRPC(){}
void ExampleServiceRPC::destroy(){}
void ExampleServiceRPC::request(
ChannelRPCRequester::shared_pointer const & channelRPCRequester,
epics::pvData::PVStructure::shared_pointer const & pvArgument)
{
String buffer;
PVStringPtr pvfunction = pvArgument->getStringField("function");
PVStringArrayPtr pvnames = static_pointer_cast<PVStringArray>
(pvArgument->getScalarArrayField("names",pvString));
PVStringArrayPtr pvvalues = static_pointer_cast<PVStringArray>
(pvArgument->getScalarArrayField("values",pvString));
buffer += "pvArgument ";
bool is = true;
if(pvfunction==0) is = false;
if(pvnames==0) is = false;
if(pvvalues==0) is = false;
if(is) {
buffer += "is a NTNameValue\n";
} else {
buffer += "is not a NTNameValue\n ";
}
pvArgument->toString(&buffer);
printf("%s\n",buffer.c_str());
StandardFieldPtr standardField = getStandardField();
StandardPVFieldPtr standardPVField = getStandardPVField();
FieldCreatePtr fieldCreate = getFieldCreate();
PVDataCreatePtr pvDataCreate = getPVDataCreate();
size_t n = 5;
FieldConstPtrArray fields;
StringArray names;
fields.reserve(n);
names.reserve(n);
names.push_back("alarm");
names.push_back("timeStamp");
names.push_back("label");
names.push_back("position");
names.push_back("alarms");
fields.push_back(standardField->alarm());
fields.push_back(standardField->timeStamp());
fields.push_back(fieldCreate->createScalarArray(pvString));
fields.push_back(fieldCreate->createScalarArray(pvDouble));
fields.push_back(fieldCreate->createStructureArray(standardField->alarm()));
StructureConstPtr structure = fieldCreate->createStructure(names,fields);
PVStructurePtr pvStructure = pvDataCreate->createPVStructure(structure);
PVTimeStamp pvTimeStamp;
TimeStamp timeStamp;
pvTimeStamp.attach(pvStructure->getStructureField("timeStamp"));
timeStamp.getCurrent();
pvTimeStamp.set(timeStamp);
StringArray label;
label.reserve(2);
for(int i=0; i<2; i++) {
label.push_back(names[i+3]);
}
PVStringArrayPtr pvLabel = static_pointer_cast<PVStringArray>
(pvStructure->getScalarArrayField("label",pvString));
pvLabel->put(0,2,label,0);
PVDoubleArrayPtr pvPositions = static_pointer_cast<PVDoubleArray>
(pvStructure->getScalarArrayField("position",pvDouble));
double positions[2];
positions[0] = 1.0;
positions[1] = 2.0;
pvPositions->put(0,2,positions,0);
PVStructureArrayPtr pvAlarms = static_pointer_cast<PVStructureArray>
(pvStructure->getStructureArrayField("alarms"));
PVAlarm pvAlarm;
Alarm alarm;
PVStructurePtrArray palarms;
size_t na=2;
palarms.reserve(na);
for(size_t i=0; i<na; i++) {
palarms.push_back(pvDataCreate->createPVStructure(standardField->alarm()));
}
for(size_t i=0; i<na; i++) {
pvAlarm.attach(palarms[i]);
alarm.setMessage("test");
alarm.setSeverity(majorAlarm);
alarm.setStatus(clientStatus);
pvAlarm.set(alarm);
}
pvAlarms->put(0,2,palarms,0);
String labels[2];
labels[0] = pvPositions->getFieldName();
labels[1] = pvAlarms->getFieldName();
pvLabel->put(0,2,labels,0);
buffer.clear();
pvStructure->toString(&buffer);
printf("%s\n",buffer.c_str());
channelRPCRequester->requestDone(Status::OK,pvStructure);
}
The only interesting method is request. The example first looks to see if the argument has the following fields
The idea is that the client passes a function name and a set of name,value pairs as arguments,
The example creates a structure that represents a table. It has fields:
This structure is returned to the client. Note that channelRPCRequester->requestDone(Status::Ok,pvStructure) must be called.
The Make file shows how to build as both a standalone application as as part of a V3 IOC.\
exampleServiceRPCMain.cpp is the main program for a standalone application.
void example()
{
PVServiceChannelCTX::shared_pointer myCTX
= PVServiceChannelCTX::shared_pointer(new PVServiceChannelCTX());
ExampleServiceRPC::shared_pointer example
= ExampleServiceRPC::shared_pointer(new ExampleServiceRPC());
ServiceChannelRPC::shared_pointer serviceChannelRPC
= ServiceChannelRPC::shared_pointer(
new ServiceChannelRPC("serviceRPC",example));
cout << "serviceRPC\n";
string str;
while(true) {
cout << "Type exit to stop: \n";
getline(cin,str);
if(str.compare("exit")==0) break;
}
}
int main(int argc,char *argv[])
{
example();
return 0;
}
The first statement MUST be present. It starts the pvAccess server. The second statement creates the service. The next statement is what installs the service. The remaining statements just wait for the request to terminate the main program. Except for "ExampleService" any service should have similar code.
exampleServiceRegister.dbd and exampleServiceRegister.cpp are required to run the service as part os a V3 IOC. The dbd file is:
registrar("startExampleServiceRegister")
This is the dbd statement that will have the V3 IOC register the code desctribed in the next file:
The cpp file is:
/* define arguments for the following command:
* startExampleService channelName
*/
static const iocshArg startExampleServiceArg0 = {"channelName", iocshArgString};
static const iocshArg *const startExampleServiceArgs[] = {
&startExampleServiceArg0};
static const iocshFuncDef startExampleServiceFuncDef = {
"startExampleService", 1, startExampleServiceArgs};
static void startExampleService(const iocshArgBuf *args)
{
// the channel name is the single argument which is a string
const char * channelName = args[0].sval;
if(channelName==0 || channelName[0]==0) {
printf("illegal channelName\n");
return;
}
ExampleServiceRPC::shared_pointer example
= ExampleServiceRPC::shared_pointer(new ExampleServiceRPC());
ServiceChannelRPC *serviceChannelRPC
= new ServiceChannelRPC("serviceRPC",example);
}
static void startExampleServiceRegister(void)
{
static int firstTime = 1;
if (firstTime) {
firstTime = 0;
iocshRegister(&startExampleServiceFuncDef, startExampleService);
}
}
epicsExportRegistrar(startExampleServiceRegister);
Again any service can provide similar code. Just change ExampleService to something else.
An example client that calls the exampleServerRPC is also in pvIOCCPP/testApp/exampleServiceRPC.
The client code uses the ezchanelRPC support discussed in a previous section.
The code for the client example is in pvIOCCPP/testApp/exampleServiceRPC/exampleClient.cpp.