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 27-Sep-2012 version of the C++ implementation of pvIOC. This release implements only the following:
The following is a list of unresolved issues 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:
After the minimum featues the following should be implemented:
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.
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".
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
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);
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
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
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:
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
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
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
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
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
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
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
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
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:
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:
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,
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:
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 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
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.