This product is made available subject to acceptance of the EPICS open source license.
This document describes pvDatabaseCPP, which is a framework for implementing a network accessable database of smart memory resident records. Network access is via pvAccess. The data in each record is a top level PVStructure as defined by pvData. The framework includes a complete implementation of ChannelProvider as defined by pvAccess. The framework can be extended in order to create record instances that implements services. The minimum that an extenson must provide is a top level PVStructure and a process method.
EPICS version 4 is a set of related products in the EPICS
V4 control system programming environment:
relatedDocumentsV4.html
This is the 25-Jul-2013 version of the definition of pvDatabaseCPP.
NOTE: This is built against pvDataCPP-md NOT against pvDataCPP. To build you must also checkout pvAccessCPP and build it against pvDataCPP-md.
All channel methods except channelRPCi, which is implemented by pvAccess, have been implemented. This project is ready for alpha users.
Future enhancements in priority order:
A brief description of a pvDatabase is that it is a set of network accessible, smart, memory resident records. Each record has data composed of a top level PVStructure. Each record has a name which is the channelName for pvAccess. A local Channel Provider implements the complete ChannelProvider and Channel interfaces as defined by pvAccess. The local provider provides access to the records in the pvDatabase. This local provider is accessed by the remote pvAccess server. A record is smart because code can be attached to a record, which is accessed via a method named process.
This document describes components that provide the following features:
database provides base classes that make it easy to create record instances. The code attached to each record must create the top level PVStructure and the following three methods:
Included with this project are two examples that are useful for seeing how pvDatabase can be used by clients. Each can be deployed either as a standalone process or as part of a V3IOC. The examples are:
To start exampleCounterMain:
mrk> pwd /home/hg/pvDatabaseCPP mrk> bin/linux-x86_64/exampleCounterMain
To start exampleCounter as part of a V3IOC:
mrk> pwd /home/hg/pvDatabaseCPP/iocBoot/exampleCounter mrk> ../../../bin/linux-x86_64/exampleCounter st.cmd
You can then issue the commands dbl and pvdbl:
epics> dbl double01 epics> pvdbl exampleCounter epics>double01 is a v3Record. exampleCounter is a pvRecord.
Starting exampleServer is similar. After successfully running exampleCounterMain and exampleCounter then try starting exampleServerMain and exampleServer.
The Java program swtshell can be used to access pvDatabase.
In particular read the sections "Getting Started" and "Simple Example". They will work on the exampleServer with the following differences:
The exampleServer pvDatabase includes the following records:
It also has a number of other scalar and array records.
This document descibes a C++ implementation of some of the components in pvIOCJava, which also implements a pvDatabase. PVDatabaseCPP extracts the core components required to create a network accessible database of smart memory resident records. pvDatabaseCPP does not implement any of the specialized support that pvIOCJava provides. It is expected that many services will be created that do not require the full features provided by pvIOCJava. In the future pvIOCJava should be split into multiple projects with one of them named pvDatabaseJava.
Similar to epics base, pvIOCJava implements the concept of synchronous and asynchronous record processing. For pvDatabaseCPP the process method is allowed to block. Until a need is demonstrated this will remain true. The main user of a pvDatabase is pvAccess, and in particular, remote pvAccess. The server side of remote pvAccess creates two threads for each client and always accesses a record via these threads. It is expected that these threads will be sufficient to efficently handle all channel methods except channelRPC. For channelRPC pvAccess provides (or will provide) a thread pool for channelRPC requests. If, in the future, a scanning facility is provided by pvDatabaseCPP or some other facility, then the scanning facility will have to provide some way of handling process requests that block.
The example implements a simple counter. The example can be run on linux as follows:
mrk> pwd /home/hg/pvDatabaseCPP mrk> bin/linux-x86_64/exampleCounter
The example consists of four components:
The example resides in src/database. The complete implementation is in the header file. A serious implementation might break the code into a header and an implementation file.
The description consists of
class ExampleCounter;
typedef std::tr1::shared_ptr<ExampleCounter> ExampleCounterPtr;
class ExampleCounter :
public PVRecord
{
public:
POINTER_DEFINITIONS(ExampleCounter);
static ExampleCounterPtr create(
epics::pvData::String const & recordName);
virtual ~ExampleCounter();
virtual void destroy();
virtual bool init();
virtual void process();
private:
ExampleCounter(epics::pvData::String const & recordName,
epics::pvData::PVStructurePtr const & pvStructure);
epics::pvData::PVLongPtr pvValue;
epics::pvData::PVTimeStamp pvTimeStamp;
epics::pvData::TimeStamp timeStamp;
};
where
The implementation of create method is:
ExampleCounterPtr ExampleCounter::create(
epics::pvData::String const & recordName)
{
epics::pvData::PVStructurePtr pvStructure =
epics::pvData::getStandardPVField()->scalar(
epics::pvData::pvDouble,"timeStamp,alarm"");
ExampleCounterPtr pvRecord(
new ExampleCounter(recordName,pvStructure));
if(!pvRecord->init()) pvRecord.reset();
return pvRecord;
}
This:
The private constructor method is:
ExampleCounter::ExampleCounter(
epics::pvData::String const & recordName,
epics::pvData::PVStructurePtr const & pvStructure)
: PVRecord(recordName,pvStructure)
{
pvTimeStamp.attach(pvStructure->getSubField("timeStamp"));
}
The example is very simple. Note that it calls the base class constructor.
The destructor and destroy methods are:
ExampleCounter::~ExampleCounter()
{
}
void ExampleCounter::destroy()
{
PVRecord::destroy();
}
The destructor has nothing to do.
The destroy method, which is virtual, just calls the destroy method of the base class.
A more complicated example can clean up any resources it used but must call the base
class destroy method.
The implementation of init is:
bool ExampleCounter::init()
{
initPVRecord();
epics::pvData::PVFieldPtr pvField;
pvValue = getPVStructure()->getLongField("value");
if(pvValue.get()==NULL) return false;
return true;
}
This:
The implementation of process is:
void ExampleCounter::process()
{
pvValue->put(pvValue->get() + 1.0);
timeStamp.getCurrent();
pvTimeStamp.set(timeStamp);
}
It adds 1.0 to the current value.
It then sets the timeStamp to the current time.
NOTE: This is a shorter version of the actual code. It shows the essential code. The actual example shows how create an additional record.
The main program is:
int main(int argc,char *argv[])
{
PVDatabasePtr master = PVDatabase::getMaster();
ChannelProviderLocalPtr channelProvider = ChannelProviderLocal::create();
String recordName("exampleCounter");
PVRecordPtr pvRecord = ExampleCounter::create(recordName);
bool result = master->addRecord(pvRecord);
cout << "result of addRecord " << recordName << " " << result << endl;
pvRecord.reset();
startPVAServer(PVACCESS_ALL_PROVIDERS,0,true,true);
cout << "exampleCounter\n";
string str;
while(true) {
cout << "Type exit to stop: \n";
getline(cin,str);
if(str.compare("exit")==0) break;
}
return 0;
}
This:
This has two subdirectories:
The src directory has the following components:
include "base.dbd"
include "PVAServerRegister.dbd"
registrar("exampleCounterRegister")
This includes the dbd components required from base,
the dbd file for starting pvAccess and the local channelProvider,
and the dbd file for the example. The later is for the code from the
next file.
exampleCounterCreateRecord recordName
This documentation describes the first phase of a phased implementation of pvDatabaseCPP:
Future phases of pvDatabaseCPP might include:
The completion of each phase provides useful features that can be used without waiting for the completion of later phases. The rest of this document discusses only the first phase.
The first phase will only implement record processing, i. e. the process method has to do everything itself without any generic field support. This will be sufficient for starting to implement services. The following are the minimium features required
The following sections describes the classes required for the first phase.
This directory has the following files:
recordName = "laptoprecordListPGRPC";
pvRecord = RecordListRecord::create(recordName);
result = master->addRecord(pvRecord);
The classes in pvDatabase.h describe a database of memory resident smart records. It describes the following classes:
Each class is described in a separate subsection.
namespace epics { namespace pvDatabase {
class PVRecord;
typedef std::tr1::shared_ptr<PVRecord> PVRecordPtr;
typedef std::map<epics::pvData::String,PVRecordPtr> PVRecordMap;
class PVRecordField;
typedef std::tr1::shared_ptr<PVRecordField> PVRecordFieldPtr;
typedef std::vector<PVRecordFieldPtr> PVRecordFieldPtrArray;
typedef std::tr1::shared_ptr<PVRecordFieldPtrArray> PVRecordFieldPtrArrayPtr;
class PVRecordStructure;
typedef std::tr1::shared_ptr<PVRecordStructure> PVRecordStructurePtr;
class PVRecordClient;
typedef std::tr1::shared_ptr<PVRecordClient> PVRecordClientPtr;
class PVListener;
typedef std::tr1::shared_ptr<PVListener> PVListenerPtr;
class RecordPutRequester;
typedef std::tr1::shared_ptr<RecordPutRequester> RecordPutRequesterPtr;
class PVDatabase;
typedef std::tr1::shared_ptr<PVDatabase> PVDatabasePtr;
NOTES:
class PVRecord
public epics::pvData::Requester,
public std::tr1::enable_shared_from_this<PVRecord>
{
public:
POINTER_DEFINITIONS(PVRecord);
virtual bool init() {initPVRecord(); return true;}
virtual void process() {}
virtual void destroy();
static PVRecordPtr create(
epics::pvData::String const & recordName,
epics::pvData::PVStructurePtr const & pvStructure);
virtual ~PVRecord();
epics::pvData::String getRecordName();
PVRecordStructurePtr getPVRecordStructure();
PVRecordFieldPtr findPVRecordField(
epics::pvData::PVFieldPtr const & pvField);
bool addRequester(epics::pvData::RequesterPtr const & requester);
bool removeRequester(epics::pvData::RequesterPtr const & requester);
inline void lock_guard() { epics::pvData::Lock theLock(mutex); }
void lock();
void unlock();
bool tryLock();
void lockOtherRecord(PVRecordPtr const & otherRecord);
bool addPVRecordClient(PVRecordClientPtr const & pvRecordClient);
bool removePVRecordClient(PVRecordClientPtr const & pvRecordClient);
void detachClients();
bool addListener(PVListenerPtr const & pvListener);
bool removeListener(PVListenerPtr const & pvListener);
void beginGroupPut();
void endGroupPut();
epics::pvData::String getRequesterName() {return getRecordName();}
virtual void message(
epics::pvData::String const & message,
epics::pvData::MessageType messageType);
void message(
PVRecordFieldPtr const & pvRecordField,
epics::pvData::String const & message,
epics::pvData::MessageType messageType);
void toString(epics::pvData::StringBuilder buf);
void toString(epics::pvData::StringBuilder buf,int indentLevel);
int getTraceLevel();
void setTraceLevel(int level);
protected:
PVRecord(
epics::pvData::String const & recordName,
epics::pvData::PVStructurePtr const & pvStructure);
void initPVRecord();
epics::pvData::PVStructurePtr getPVStructure();
PVRecordPtr getPtrSelf()
{
return shared_from_this();
}
private:
...
}
The methods are:
The protected methods are:
class PVRecordField {
public virtual epics::pvData::PostHandler,
public std::tr1::enable_shared_from_this<PVRecordField>
public:
POINTER_DEFINITIONS(PVRecordField);
PVRecordField(
epics::pvData::PVFieldPtr const & pvField,
PVRecordStructurePtr const &parent,
PVRecordPtr const & pvRecord);
virtual ~PVRecordField();
virtual void destroy();
PVRecordStructurePtr getParent();
epics::pvData::PVFieldPtr getPVField();
epics::pvData::String getFullFieldName();
epics::pvData::String getFullName();
PVRecordPtr getPVRecord();
bool addListener(PVListenerPtr const & pvListener);
virtual void removeListener(PVListenerPtr const & pvListener);
virtual void postPut();
virtual void message(
epics::pvData::String const & message,
epics::pvData::MessageType messageType);
protected:
PVRecordFieldPtr getPtrSelf()
{
return shared_from_this();
}
virtual void init();
virtual void postParent(PVRecordFieldPtr const & subField);
virtual void postSubField();
private:
...
};
When PVRecord is created it creates a PVRecordField for every field in the PVStructure that holds the data. It has the following methods:
class PVRecordStructure : public PVRecordField {
public:
POINTER_DEFINITIONS(PVRecordStructure);
PVRecordStructure(
epics::pvData::PVStructurePtr const & pvStructure,
PVRecordFieldPtrArrayPtr const & pvRecordField);
virtual ~PVRecordStructure();
virtual void destroy();
PVRecordFieldPtrArrayPtr getPVRecordFields();
epics::pvData::PVStructurePtr getPVStructure();
virtual void removeListener(PVListenerPtr const & pvListener);
virtual void postPut();
protected:
virtual void init();
private:
...
};
When PVRecord is created it creates a PVRecordStructure for every structure field in the PVStructure that holds the data. It has the following methods:
class PVRecordClient {
POINTER_DEFINITIONS(PVRecordClient);
virtual ~PVRecordClient();
virtual void detach(PVRecordPtr const & pvRecord);
};
where
class PVListener {
virtual public PVRecordClient
public:
POINTER_DEFINITIONS(PVListener);
virtual ~PVListener();
virtual void dataPut(PVRecordFieldPtr const & pvRecordField) = 0;
virtual void dataPut(
PVRecordStructurePtr const &
requested,PVRecordFieldPtr const & pvRecordField) = 0;
virtual void beginGroupPut(PVRecordPtr const & pvRecord) = 0;
virtual void endGroupPut(PVRecordPtr const & pvRecord) = 0;
virtual void unlisten(PVRecordPtr const & pvRecord);
};
where
class PVDatabase : virtual public epics::pvData::Requester {
public:
POINTER_DEFINITIONS(PVDatabase);
static PVDatabasePtr getMaster();
virtual ~PVDatabase();
virtual void destroy();
PVRecordPtr findRecord(epics::pvData::String const& recordName);
bool addRecord(PVRecordPtr const & record);
epics::pvData::PVStringArrayPtr getRecordNames();
bool removeRecord(PVRecordPtr const & record);
virtual epics::pvData::String getRequesterName();
virtual void message(
epics::pvData::String const &message,
epics::pvData::MessageType messageType);
private:
PVDatabase();
};
where
This is code that provides an implementation of channelProvider as defined by pvAccess. It provides access to PVRecords and is access by the server side of remote pvAccess.
This is a complete implementation of channelProvider and , except for channelRPC, provides a complete implementation of Channel as defined by pvAccess. For monitors it calls the code described in the following sections.
This provides code that creates a top level PVStructure that is an arbitrary subset of the fields in the PVStructure from a PVRecord. In addition it provides code that monitors changes to the fields in a PVRecord. A client configures the desired set of subfields and monitoring options via a pvRequest structure. pvAccess provides a class CreatePVRequest that creates a pvRequest. The pvCopy code provides the same functionality as the pvCopy code in pvIOCJava.
Currently all that is implemented is a header file. The only algorithm currently implemented is onPut
epics::pvData::monitor defines the monitor interfaces as seen by a client. See pvDatabaseCPP.html For details.
monitorFactory implements the monitoring interfaces for a PVRecord. It implements queueSize=0 and queueSize>=2.
The implementation uses PVCopy and PVCopyMonitor which are implemented in pvCopy. When PVCopyMonitor tells monitor that changes have occurred, monitor applies the appropriate algorithm to each changed field.
Currently only algorithm onPut is implemented but, like pvIOCJava there are plans to support for the following monitor algorithms:
MonitorFactory provides the following methods:
class MonitorFactory
{
static MonitorPtr create(
PVRecordPtr const & pvRecord,
MonitorRequester::shared_pointer const & monitorRequester,
PVStructurePtr const & pvRequest);
static void registerMonitorAlgorithmCreater(
MonitorAlgorithmCreatePtr const & monitorAlgorithmCreate,
String const & algorithmName);
}
where
This section provides two useful record support modules and one that is used for testing.
This implements a PVRecord that allows a client to set the trace level of a record. It follows the pattern of a channelPutGet record:
traceRecord
structure arguments
string recordName
int level 0
structure result
string status
where:
testExampleServerMain.cpp has an example of how to create a traceRecord:
PVDatabasePtr master = PVDatabase::getMaster(); PVRecordPtr pvRecord; String recordName; bool result(false); recordName = "traceRecordPGRPC"; pvRecord = TraceRecord::create(recordName); result = master->addRecord(pvRecord); if(!result) cout<< "record " << recordName << " not added" << endl;
This implements a PVRecord that allows a client to set the trace level of a record. It follows the pattern of a channelPutGet record:
traceRecord
structure arguments
string database master
string regularExpression .*
structure result
string status
string[] names
where:
Note that swtshell has a command channelList that requires that a record of this type is present and calls it. Thus user code does not have to use a channelGetPut to get the list of record names.
testExampleServerMain.cpp has an example of how to create a traceRecord:
recordName = "laptoprecordListPGRPC"; pvRecord = RecordListRecord::create(recordName); result = master->addRecord(pvRecord); if(!result) cout<< "record " << recordName << " not added" << endl;
This simulates a simple power supply record. It is used for testing.