EPICS pvIOCCPP

EPICS v4 Working Group, Working Draft, 27-Sep-2012

Latest version:
pvIOCCPP.html
This version:
pvIOCCPP_20120927.html
Previous version:
None
Editors:
Marty Kraimer, BNL

Abstract

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

Status of this Document

This is the 27-Sep-2012 version of the C++ implementation of pvIOC. This release implements only the following:

v3Channel
This provides pvAccess for V3 records.
channelProviderLocal
Provides a context for a pvAccess channelProvider named local.
multiValue
Support that creates channels for channelProviderLocal. Each channel has a top level Structure that has an alarm and a timeStamp field. If addition the top level structure has a a set of fields where each field is the value field of a channel provided by another provider.
service
Support for implementing services.
ezchannelRPC
Client code that issues a channelRPC request

The following is a list of unresolved issues for pvIOCCPP:

LOTS AND LOTS
Almost none of the desired features for a pvIOC are implemented. See the next section for some details.
ezchannelRPC
This is now implemented in pvAccess. Remove it from pvIOCCPP.
service
Remove everything except channelBase* and then rename directory to channelBase. In fact move channelBase to pvAccess.
v3Array
The current application does not implement shareData. The key is to implement a special C++ Allocator. This is important for beam line image applications.

Future Plans For pvIOCCPP

What is currently implemented by pvIOCCPP has almost non of the features that a pvIOC is expected to provide. Read the documentation for pvIOCJava to see what a full implementation should provide.

Other then service and ezchannelRPC, which should be moved to pvAccessCPP, the only things currently provided 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 is a major task as will be seen when we next discuss multiChannel.

multiChannel implements ChannelProvider, Channel, and ChannelGet. Each instance provides a channel that holds data from a set of channels obtained from another channelProvider. The most useful is the v3Channel provider. It then provides data from a set of V3 records. 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 minimum features provided by pvIOCJava that should be implemented by pvIOCCPP are:

database
This is a databbase of PVRecords. Each record has a name, which is the channelName, and holds a top level PVStructure.
install
This is a facility to dynamically add and delete PVRecords.
pvCopy
Creates a PVStructure that contains a copy of an arbitary subset of the fields of another top level PVStructure. It can copy data between the two and maintains a bitSet that show which fields are changed.
monitor
This provides the ability to monitor changes in the fields of a record.
support
Support makes the database smart because it attaches code to records.
This provides two features:
recordSupport
This is support that is responsible for processing a PVRecord, which means calling support attached to the record.
support
Each field of a PVRecord can optionally have code attached to it. The code is called when a record is processed. Some generic support code is provided but additional code can be provided.
localChannelProvider
localChannelProvider will access data from records using the features above. It will implement all channel methods except channelRPC.

After the minimum featues the following should be implemented:

portDriver
This is meant as the successor to the asynManager component of the asyn driver for epics V3.
xml
This is used to define PVRecords via files with xml syntax.

Table of Contents

Introduction

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:

v3Channel
This provides pvAccess for V3 records.
channelProviderLocal
This implements a channel provider named "local". It interfaces with pvAccess and allows code that implements channel instances to register channel instances with it.
multiValue
This allows the creation of channels that hold data from a set of V3 records. For now only channelGet is supported.
service
Support for implementing services. This is going to go away.
ezchannelRPC
Client code that issues a channelRPC request. This is being moved to pvAccess.

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

Introduction

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.

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".

Getting started

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:

Create an epics application
Look at the example (pvIOCCPP/exampleApp/simpleV3Channel) to see how create an application or use the MakeBaseApp that comes with EPICS base.
configure/RELEASE
Add the location of pvDataCPP, pvAccessCPP, and PVIOCCP. For example:
PVBASE=/home/mrk/hg
PVDATA=${PVBASE}/pvDataCPP
PVACCESS=${PVBASE}/pvAccessCPP
PVIOC=${PVBASE}/pvIOCCPP
v3Channel.dbd
In the src directory where your application is being built add an include statement for v3Channel.dbd. For example:
include "base.dbd"
include "v3Channel.dbd"
st.cmd
After the iocInit command in the st.cmd file add:
startV3Channel

Implementation

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.

Introduction to Implementation

The implementation consists of the following header files:

v3Channel.h
The V3Channel interface. It defines extensions for both ChannelProvider and Channel.
v3Util.h
The interace for transfering data between V3 records and pvData, i. e. the code that converts between the V3 data and pvData.
v3Array.h
Because arrays are more complex than scalar data they are handled by a separate interface instead of by v3Util itself.
v3CAMonitor.h
db_post_event is implement in code (dbEvent) that passes events directly to CAV3. Thus the only way V3Channel can trap monitors is via CAV3. V3Channel only uses CAV3 for scalar numeric data, timeStamp, and alarms. All other monitored data is obtained from the V3 record itself.
CAV3Context.h
This is code that interfaces between pvAccess threads and CAV3 context.

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.

Remaining tasks

The main remaining tasks are:
Access Security
Since V3Channnel is not using any of the remote part of V3CA, it "sidesteps" access security. The V3 access security system is independent of CAV3 so V3Channel will be able to implement access security without going through V3CA but this has not been done.
Monitor Algorithm
pvIOCJava supports an extensible set of monitor algorithms. It provides support for onChange and onPercentChange but allows additional algorithms. This same support will be implemented by pvIOCCP.
V3Channel::init
This creates the introspection interface that is returned by getField. Currently it always has property fields alarm, timeStamp, and display. If the field is not an array it also has a property field control. It might be better to look at the record support entry table to see if display and control should be included.

Main Concepts

In epics V3 there are two interfaces for accessing data:

db_access
This defines all the DBR_XXX types and associated C structures as seen by CAV3 clients. V3Channel, with the exception of V3CAMonitor does not use this. Even for monitors only a few of the DBR_XXX types are used.
dbAccess
This and some additional interfaces like the RSET (record support entry table) and the DBADDR are what V3Channel uses to get/put data from the V3 records. Wherever possible data is accessed via the DBADDR and RSET. pvAccess method pvPutField is used to put data to fields that have "side effects". For example writing to the SCAN field has side effects.

v3Channel.h

This defines two interfaces:

V3ChannelProvider
The implementation of ChannelProvider.
V3Channel
The implementation of Channel.

V3ChannelProvider is defined as:

class V3ChannelProvider :
public epics::pvAccess::ChannelBaseProvider
{
public:
POINTER_DEFINITIONS(V3ChannelProvider);
V3ChannelProvider();
virtual ~V3ChannelProvider();
virtual epics::pvAccess::ChannelFind::shared_pointer channelFind(
epics::pvData::String const &channelName,
epics::pvAccess::ChannelFindRequester::shared_pointer const & channelFindRequester);
virtual epics::pvAccess::Channel::shared_pointer createChannel(
epics::pvData::String const &channelName,
epics::pvAccess::ChannelRequester::shared_pointer  const &channelRequester,
short priority,
epics::pvData::String const &address);
};

where

getChannelProvider
This returns the single instance od the V3ChannelProvider.
getProviderName
This returns "v3Channel"
channelFind
Calls dbNameToAddr to see if the requested channelName is in the V3 database. If calls channelFindRequester->channelFindResult to report the result. It always returns null.
createChannel
Calls dbNameToAddr to see if the requested channelName is in the V3 database. If not it calls channelRequester->channelCreated(notFoundStatus,0) and returns null. If it is in the V3 database it creates a new V3Channel, calls init, calls channelRequester->channelCreated(Status::Ok,v3Channel) and returns the newly created V3Channel.

V3Channel is defined as:

class V3Channel :
public virtual epics::pvAccess::ChannelBase
{
public:
POINTER_DEFINITIONS(V3Channel);
V3Channel(
epics::pvAccess::ChannelBaseProvider::shared_pointer const & channelProvider,
epics::pvAccess::ChannelRequester::shared_pointer const & requester,
epics::pvData::String const & name,
std::auto_ptr<DbAddr> addr
);
virtual ~V3Channel();
void init();
virtual void getField(
epics::pvAccess::GetFieldRequester::shared_pointer const &requester,
epics::pvData::String const &subField);
virtual epics::pvAccess::ChannelProcess::shared_pointer createChannelProcess(
epics::pvAccess::ChannelProcessRequester::shared_pointer const &channelProcessRequester,
epics::pvData::PVStructure::shared_pointer const &pvRequest);
virtual epics::pvAccess::ChannelGet::shared_pointer createChannelGet(
epics::pvAccess::ChannelGetRequester::shared_pointer const &channelGetRequester,
epics::pvData::PVStructure::shared_pointer const &pvRequest);
virtual epics::pvAccess::ChannelPut::shared_pointer createChannelPut(
epics::pvAccess::ChannelPutRequester::shared_pointer const &channelPutRequester,
epics::pvData::PVStructure::shared_pointer const &pvRequest);
virtual epics::pvData::Monitor::shared_pointer createMonitor(
epics::pvData::MonitorRequester::shared_pointer const &monitorRequester,
epics::pvData::PVStructure::shared_pointer const &pvRequest);
virtual epics::pvAccess::ChannelArray::shared_pointer createChannelArray(
epics::pvAccess::ChannelArrayRequester::shared_pointer const &channelArrayRequester,
epics::pvData::PVStructure::shared_pointer const &pvRequest);
virtual void printInfo();
virtual void printInfo(epics::pvData::StringBuilder out);
private:
std::auto_ptr<DbAddr> dbAddr;
epics::pvData::FieldConstPtr recordField;
};

where

V3Channel
This is the constructor called by V3ChannelProvider.
init
This is called by V3ChannelProvider after it has created the V3Channel. It's primary function is to create a FieldConstPtr that provides the introspection interface for the field being accessed. This is what is returned by getField. It is always a structure with a value field that has a type determined by the V3 field being accessed. It always has property fields value, timeStamp, alarm, and display. In addition if the value field is not an array a control property field. NOTE: It might be better to look at the record support entry table to determine what property fields to include.
destroy
This is called by whoever accesses the V3Channel. For example remote PVAccess calls this when the client disconnects from the channel.
getRequesterName
Get the requester that created the V3Channel.
message
This is a method that can be called by any code associated with the V3Channel. It just passes the message to the requester.
getProvider
This returns the provider which is V3ChannelProvider.
getRemoteAddress
This returns String("local").
getConnectionStatus
This returns Channel::CONNECTED;
getChannelRequester
This gets the requester that asked to create the channel.
isConnected
Always returns true.
getField
This returns the introspection interface created by init.
getAccessRights
This is currently throws an exception. As stated in the TODO list this should interact with the V3 access security system.
createChannelProcess
This creates a V3ChannelProcess and then call V3ChannelProcess::init. Process is implemented via a V3 dbPutNotify to the .PROC field of the record.
createChannelGet
This creates a V3ChannelGet and then calls V3ChannelGet::init. The implementation uses V3Util to do most of the work. See V3Util below for details. If an option to process is given then the record is processed via a dbPutNotify to the PROC field before data is fetched. Note that display and control data is only returned for the first get, i.e. display and control data do not change.
createChannelPut
This creates a V3ChannelPut and then calls V3ChannelPut::init. The implementation uses V3Util to do most of the work. See V3Util below for details. It is responsible for creating the PVStructure that holds the data and for getting/putting data between the V3 record and the PVStructure. Although properties alarm, timeStamp, control, and display can be requested they can not be modified because V3 does not allow them to be changed by a client. If a V3 dbPut to a field has side effects than a put results in a call to the V3 dbPut. Examples are a put to the SCAN field. It actually changes the scanning mechanism for the record. If a V3 dbPut does not have side effects other than causing a record to process a put does NOT result in a call to the V3 dbPut. Instead processing is determined by the pvAccess client. If the client requests processing the normal pvAccess semantics are used, i. e. the record is processed via a dbPutNotify to the PROC field after the data is written into the V3 record.
createChannelPutGet
This is not implemented since it does not make sense for V3 records.
createChannelRPC
This is not implemented since it does not make sense for V3 records.
createMonitor
This is implemented via a monitior request call to CAV3. See V3CaMonitor below for details.
createChannelArray
This creates a V3ChannelArray that implements the normal ChannelArray semantics. It uses DBADDR and the RSET to direcly access the array in the V3 record.
printInfo
This just prints a short message.

v3Util.h

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:

processBit
The client has requested that the record be processed as part of a ChannelGet::get or a ChannelPut::put.
shareArrayBit
The client has specified that array data be shared, e. g. has set record[shareData=true], In this case no copy of the array data is created. Instead data is transfered directly between network buffers and the data array in the V3 record. If this is specified the client must understand that if an array is split between network buffers the array may change between buffers.
timeStampBit
The requester has requsted the timeStamp.
alarmBit
The requester has requested alarm info.
displayBit
The requseter has requested display info.
controlBit
The requester has requested control info.
scalarValueBit
The V3 field is a scalar.
arrayValueBit
The V3 field is an array.
enumValueBit
The V3 field is a DBF_ENUM, DBF_MENU, or DBF_DEVICE.
noAccessBit
The V3 field is a no access field. The client will not be allowed any access to the field.
noModBit
The V3 field is a no mod field. The client will not be allowed to modify the field.
dbPutBit
Side effects occur if a dbPut is issued to the field. ChannelPut will issue a dbPutField for such fields instead of directly modifying the field..
isLinkBit
Is the field a link field? Link fields require special handling for both get and put.

V3Util is defined as:

class V3Util {
public:
static int getProperties(
epics::pvData::Requester::shared_pointer const &requester,
epics::pvData::PVStructure::shared_pointer const &pvRequest,
DbAddr &dbAddr);
static epics::pvData::PVStructurePtr createPVStructure(
epics::pvData::Requester::shared_pointer const &requester,
int mask,DbAddr &dbAddr);
static void getPropertyData(
epics::pvData::Requester::shared_pointer const &requester,
int mask,DbAddr &dbAddr,
epics::pvData::PVStructurePtr const &pvStructure);
static epics::pvData::Status get(
epics::pvData::Requester::shared_pointer const &requester,
int mask,DbAddr &dbAddr,
epics::pvData::PVStructurePtr const &pvStructure,
epics::pvData::BitSet::shared_pointer const &bitSet,
CAV3Data *caV3Data);
static epics::pvData::Status put(
epics::pvData::Requester::shared_pointer const &requester,
int mask,DbAddr &dbAddr,
epics::pvData::PVFieldPtr const &pvField);
static epics::pvData::Status putField(
epics::pvData::Requester::shared_pointer const &requester,
int mask,DbAddr &dbAddr,
epics::pvData::PVFieldPtr const &pvField);
static epics::pvData::ScalarType getScalarType(
epics::pvData::Requester::shared_pointer const &requester,
DbAddr &dbAddr);
};

where

getProperties
As mentioned that computes the mask discussed above.
createPVStructure
This method creates a PVStructure that holds data that is transfered between pvAccess and the V3 record. It uses a combination of the mask computed by gerProperties and the V3 DbAddr to determine the contents of the PVStructure.
getPropertyData
This gets display and control info from the V3 record. Note that this is called only once by V3ChannelGet, V3ChannelPut, and V3ChannelMonitor. This only the first get or monitor will cause the property data to be sent to the client.
get
This gets data from the V3 record and puts it into the PVStructure.
put
This takes data from the PVStructure and puts it into the V3 record. Only the value field can be changed.
putField
This transfers data from the PVfield to the V3 field via a call to dbPutField.
getScalarType
This gets the scalar type associated for the V3 field being accessed.

v3Array.h

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);
epics::pvData::PVByteArrayPtr createByteArray(
epics::pvData::PVStructurePtr const & parent,
epics::pvData::ScalarArrayConstPtr const & scalar,
DbAddr &dbAddr,bool shareData);
epics::pvData::PVUByteArrayPtr createUByteArray(
epics::pvData::PVStructurePtr const & parent,
epics::pvData::ScalarArrayConstPtr const & scalar,
DbAddr &dbAddr,bool shareData);
epics::pvData::PVShortArrayPtr createShortArray(
epics::pvData::PVStructurePtr const & parent,
epics::pvData::ScalarArrayConstPtr const & scalar,
DbAddr &dbAddr,bool shareData);
epics::pvData::PVUShortArrayPtr createUShortArray(
epics::pvData::PVStructurePtr const & parent,
epics::pvData::ScalarArrayConstPtr const & scalar,
DbAddr &dbAddr,bool shareData);
epics::pvData::PVIntArrayPtr createIntArray(
epics::pvData::PVStructurePtr const & parent,
epics::pvData::ScalarArrayConstPtr const & scalar,
DbAddr &dbAddr,bool shareData);
epics::pvData::PVUIntArrayPtr createUIntArray(
epics::pvData::PVStructurePtr const & parent,
epics::pvData::ScalarArrayConstPtr const & scalar,
DbAddr &dbAddr,bool shareData);
epics::pvData::PVFloatArrayPtr createFloatArray(
epics::pvData::PVStructurePtr const & parent,
epics::pvData::ScalarArrayConstPtr const & scalar,
DbAddr &dbAddr,bool shareData);
epics::pvData::PVDoubleArrayPtr createDoubleArray(
epics::pvData::PVStructurePtr const & parent,
epics::pvData::ScalarArrayConstPtr const & scalar,
DbAddr &dbAddr,bool shareData);
// Note that V3StringArray can not share
epics::pvData::PVStringArrayPtr createStringArray(
epics::pvData::PVStructurePtr const & parent,
epics::pvData::ScalarArrayConstPtr const & scalar,
DbAddr &dbAddr);
};

typedef std::tr1::shared_ptr<V3ValueArrayCreate> V3ValueArrayCreatePtr;

extern V3ValueArrayCreatePtr getV3ValueArrayCreate();

where

createByteArray
Create a PVByteArray attached to a DBF_CHAR or DBF_UCHAR array field.
createShortArray
Create a PVShortArray attached to a DBF_SHORT or DBF_USHORT array field.
createIntArray
Create a PVIntArray attached to a DBF_LONG or DBF_ULONG array field.
createFloatArray
Create a PVFloatArray attached to a DBF_FLOATarray field.
createDoubleArray
Create a PVDoubleArray attached to a DBF_DOUBLE array field.
createStringArray
Create a PVFloatArray attached to a DBF_STRING array field. Note that it has special semantics to handle the fixed size strings that V3 supports.

v3CAMonitor.h

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

v3Enum
A pvData enumerated structure is created for this. The monitor request will be a DBR_TIME_ENUM. For an enum a DBF_ENUM is an index. For pvData it is the index field on an enumerated structure.
v3Byte
The monitor request will be a DBR_TIME_CHAR.
v3Short
The monitor request will be a DBR_TIME_SHORT.
v3Int
The monitor request will be a DBR_TIME_LONG.
v3Float
The monitor request will be a DBR_TIME_FLOAT.
v3Double
The monitor request will be a DBR_TIME_DOUBLE.
v3String
The monitor request will be a DBR_TIME_STRING. Note that for strings data is taken from the record not from CAV3.

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
epics::pvData::int8   byteValue;
epics::pvData::uint8  ubyteValue;
epics::pvData::uint16 ushortValue;
epics::pvData::int16  shortValue;
epics::pvData::int32  intValue;
epics::pvData::uint32 uintValue;
float                 floatValue;
double                doubleValue;
};
epicsTimeStamp  timeStamp;
int             sevr;
int             stat;
const char *    status;
};

where

union
This holds a scalar numeric value obtained from CAV3. If the field is an array or a DBF_STRING then the actual data is taken from the record via the DBADDR and RSET.
timeStamp
The epics timestamp obtained from CAV3.
sevr
The severity obtained from CAV3.
stat
The status obtained from CAV3.
status
The string value associated with stat. It is taken from epicsAlarmConditionStrings, which is an extrernal that is part of dbAccess.

Next is an interface that provides c++ callbacks that can be called by the C callbacks that CAV3 calls:

class CAV3MonitorRequester : public virtual epics::pvData::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

exceptionCallback
Called when CAV3 calls the exception callback.
connectionCallback
Called when CAV3 calls the connection callback.
accessRightsCallback
Called when CAV3 calls the access rights callback,
eventCallback
Called when CAV3 calls the event callback. The data in CAV3Data is updated before this is called,

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:
class CAV3MonitorPvt *pImpl;
};

where

connect
Ask to connect. This calls ca_create_channel and ca_replace_access_rights_event.
start
This calls ca_create_subscription.
stop
This calls ca_clear_subscription.
getStatusString
This returns ca_message(status),
hasReadAccess
This calls ca_read_access. This will always be true because CAV3 treats this a local rather than remote client.
hasWriteAccess
This calls ca_write_access. This will always be true because CAV3 treats this a local rather than remote client.
isConnected
This calls ca_state to determine connection state. If connected this should always return true.

CAV3Context.h

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 V3CAContext is to manage this interaction with CAV3.

For each thread that V3Channel uses and that makes a CreateMonitor V3CAContext 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 V3CAContext exits is the V3CAContext 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

V4CAContext is defined as:

class CAV3Context {
public:
POINTER_DEFINITIONS(CAV3Context);
~CAV3Context();
void release();
void stop();
void exception(epics::pvData::String const &message);
void checkContext();
private:
std::list<epicsThreadId> auxThreadList ;
epics::pvData::Mutex mutex;
CAV3Context(
epics::pvData::RequesterPtr const & requester);
epics::pvData::RequesterPtr requester;
epicsThreadId threadId;
struct ca_client_context *context;
int referenceCount;
friend class CAV3ContextCreate;
};

where

~CAV3Context
This does nothing.
release
This is called by CAV3Monitor when a monitor is being destroyed.
stop
This is called when the thread that created the CAV3Context exits. That is when ca_context_destroy is called.
exception
This is called when CAV3 calls the exception callback that V3CAContext registers.
checkContext
This is called before any call to CAV3. It checks to see if the calling thread is aleady known to CAV3. If not ca_attach_contex is called.

There is a separate class for creating a context:

class CAV3ContextCreate {
public:
static CAV3ContextPtr get(epics::pvData::RequesterPtr const & requester);
private:
static void erase(epicsThreadId id);
static std::map<epicsThreadId,CAV3ContextPtr> contextMap;
static epics::pvData::Mutex mutex;
friend class CAV3Context;
};

where

get
This checks to see if the thread has already known to CAV3, i. e. if a CAV3Context altready exists for the calling thread. If so it is returned. If not a new CAV3Context is created and returned.

channelProviderLocal

Introduction

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:

iocsh command
It provides a V3 ioc shell command that starts a single instance of channelProvider local.
registerProvider
It provides a method registerProvider which is called by the code that makes data available to pvAccess clients. This code DOES not have to do anything except implement the channelFind and channelCreate methods of ChannelBaseProvider. It does not need to create a context and/or register itself with pvAccess.
pvAccess context
The pvAccess remote server requires that each provider provide a context, which means a thread. channelProviderLocal provides a common context for every provider that calls registerProvider.

Usage

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);

channelProviderLocal.h

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);

multiValue

Example

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]

Introduction

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]

Usage

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

createMultiValueChannel

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
    ...
channelProvider
The name of the channelProvider from which data is collected.
name
The channel name for the multiValue instance.
channelValue
This must appear after the previous two lines and must be followed by a line for each channel to collect.
fieldName
The name of the field in the multiChannel top level structure.
channelName
The channelName for the channelValueProvider.

multiValue.h

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.

valueChannel.h

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.

service

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:

RPC Service
Support for channelRPC services.
ChannelBase
Base classes for Channel and ChannelProvider. The channel create methods just issue an error to the client. A service can extend it and implement only the methods required by the service.
PVServiceProvider
A class that is a complete implementation of ChannelProvider. It extends ChannelBaseProvider. It can be used by services that create their own channelNames with an associated top level structure.

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.

service.h

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:

destroy
Called when service is being destroyed.
request
This is called when a client issues a channelRPC request.

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:

ServiceChannelRPC
The service must call this constructor. channelName is the name of the service. Note that the ServiceRPC implemention must be passed to this constructor.
destroy
Can be called to destroy the service.

channelBase.h

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

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:
    POINTER_DEFINITIONS(ChannelBaseProvider);
    ChannelBaseProvider(
        String 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;
    ChannelBaseProvider::shared_pointer getChannelProvider()
    {
        return channelProviderPtr;
    }
    // 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 channel);
    void removeChannel(ChannelBase &channel);
};

The service must implement the following two ChannelProvider methods:

channelFind
The service must keep a list or directory the channel names it supports. channelFind looks at the list or directory to see if the requested channelName is one that it supports. It calls channelFound to report the result.
createChannel
This creates a new Channel if the requested channelName is a name that the service supports. createChannel calls either channelNotCreated or channelCreated.

channelBaseProvider implements the following methods:

channelFound
This is called to report the channelFind request.
channelNotCreated
This is called if createChannel fails.
channelCreated
This is called when a new channel is created. The channel is put on a list so that when channelBaseProvider is destroyed it can destroy each channel.
removeChannel
This must be called when a channel is destroyed. Note that channels are normally destroyed by remote pvAccess rather than because channelBaseProvider is destroyed.

ChannelBase

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,
        epics::pvData::String const & channelName
    );
    virtual ~ChannelBase();
    virtual void destroy();
    virtual epics::pvData::String getRequesterName();
    virtual void message(
        epics::pvData::String const & message,
        epics::pvData::MessageType messageType);
    virtual ChannelProvider::shared_pointer const & getProvider();
    virtual epics::pvData::String getRemoteAddress();
    virtual Channel::ConnectionState getConnectionState();
    virtual epics::pvData::String getChannelName();
    virtual ChannelRequester::shared_pointer const & getChannelRequester();
    virtual bool isConnected();
    virtual void getField(
        GetFieldRequester::shared_pointer const &requester,
        epics::pvData::String const & subField);
    virtual AccessRights getAccessRights(
        epics::pvData::PVField::shared_pointer const &pvField);
    virtual ChannelProcess::shared_pointer createChannelProcess(
        ChannelProcessRequester::shared_pointer const &requester,
        epics::pvData::PVStructure::shared_pointer const &pvRequest);
    virtual ChannelGet::shared_pointer createChannelGet(
        ChannelGetRequester::shared_pointer const &requester,
        epics::pvData::PVStructure::shared_pointer const &pvRequest);
    virtual ChannelPut::shared_pointer createChannelPut(
        ChannelPutRequester::shared_pointer const &requester,
        epics::pvData::PVStructure::shared_pointer const &pvRequest);
    virtual ChannelPutGet::shared_pointer createChannelPutGet(
        ChannelPutGetRequester::shared_pointer const &requester,
        epics::pvData::PVStructure::shared_pointer const &pvRequest);
    virtual ChannelRPC::shared_pointer createChannelRPC(
        ChannelRPCRequester::shared_pointer const &requester,
        epics::pvData::PVStructure::shared_pointer const &pvRequest);
    virtual epics::pvData::Monitor::shared_pointer createMonitor(
        epics::pvData::MonitorRequester::shared_pointer const &requester,
        epics::pvData::PVStructure::shared_pointer const &pvRequest);
    virtual ChannelArray::shared_pointer createChannelArray(
        ChannelArrayRequester::shared_pointer const &requester,
        epics::pvData::PVStructure::shared_pointer const &pvRequest);
    virtual void printInfo();
    virtual void printInfo(epics::pvData::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(epics::pvData::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(epics::pvData::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:

addChannelProcess
If channelProcess is supported then this must be called whenever createChannelProcess is successful.
addChannelGet
If channelGet is supported then this must be called whenever createChannelGet is successful.
addChannelPut
If channelPut is supported then this must be called whenever createChannelPut is successful.
addChannelPutGet
If channelPutGet is supported then this must be called whenever createChannelPutGet is successful.
addChannelMonitor
If channelMonitor is supported then this must be called whenever createChannelMonitor is successful.
addChannelRPC
If channelRPC is supported then this must be called whenever createChannelRPC is successful.
addChannelArray
If channelArray is supported then this must be called whenever createChannelArray is successful.
removeChannelProcess
This is called when a channelProcess is destroyed.
removeChannelGet
This is called when a channelGet is destroyed.
removeChannelPut
This is called when a channelPut is destroyed.
removeChannelPutGet
This is called when a channelPutGet is destroyed.
removeChannelMonitor
This is called when a channelMonitor is destroyed.
removeChannelRPC
This is called when a channelRPC is destroyed.
removeChannelArray
This is called when a channelArray is destroyed.

pvServiceProvider.h

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.

ServicePVTop

This is the class that a service provider must implement.

class ServicePVTop
{
public:
    POINTER_DEFINITIONS(ServicePVTop);
    virtual ~ServicePVTop(){}
    virtual epics::pvData::String getName() = 0;
    virtual ChannelBase::shared_pointer createChannel(
        epics::pvAccess::ChannelRequester::shared_pointer const &requester,
        std::tr1::shared_ptr<PVServiceProvider> const &provider) = 0;
    virtual void destroy() = 0;
};

where

getName
Get the channel name.
createChannel
Create a channel.
destroy
This is called when the serviceTop itself is being destroyed.

PVServiceProvider.

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:

addRecord
removeRecord

Each takes an argument ServicePVTop.

PVServiceChannelCTX.

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.

ezchannelRPC.h

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

EZChannelRPC
The constructor. The only required argument is the name of the service.
destroy
This breaks the connection to the server.
connect
Connect to the server. This calls issueConnect and waitConnect. This it blocks until connected of timeout occurs. If false is returned then getMessage can be called to find out why the request failed.
issueConnect
Make a connect request by do not block. waitConnect must be called to get the result
waitConnect
This blocks until connected of timeout occurs. If false is returned then getMessage can be called to find out why the request failed.
request
This makes a channelRPC request. pvArgument is the argument for the request. This method just calls issueRequest and waitRequest. Thus it blocks. If false is returned then getMessage can be called to find out why the request failed.
issueRequest
Makes a channelRPC request and returns immediately. waitRequest must be called to get the result.
waitRequest
Wait for the channelRPC to complete. If false is returned then getMessage can be called to find out why the request failed.

exampleServiceRPC

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

exampleServiceRPC

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

function
A string field that represents what the server should do,
names
An array of string names.
values
An array of vales

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:

alarm
An alarm field is used to report problems to the client,
timeStamp
The timeStamp for the request.
label
An array of string that is a header for each column of the table. This field is followed by a set of scalarArray or structureArray fields. The number of fields is equal to the lengh of label. Each array field has the same length. For the example there are two additional fields.
position
An array for doubles that represents some kind of position
alarms
An array of alarms.

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.

exampleClient

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.