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 must be extended in order to create record instances. The minimum that an extenson must provide is a top level PVStructure and a process method but the framework provides for complex extensions.
EPICS version 4 is a set of related products in the EPICS V4 control system programming environment:
Each of these products has a Java and a C++ implementation.
This is the 11-Dec-2012 version of the definition of pvDatabaseCPP.
This is the beginning of the implementation of pvDataBaseCPP. It describes the features that will be provided. The class definitions for PVRecord are implemented. The class definition for PVDatabase are defined but not implemented.
This document descibes a C++ implementation of some of the components in pvIOCJava. It extracts the core components required to create a network accessible database of smart memory resident records. pvDatabaseCPP does not and will not implement any of the specialized support that pvIOCJava provides. Instead other projects will implement the specialized support. 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.
A brief description of a pvDatase is that it is a network accessible set of 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. This local provider is accessed by the remote pvAccess server. A record is smart because code can be attached to a record.
This document describes components that provides the following features:
database does not itself implement pvRecord instances. Instead it provides a base classes that make it easy to create record instances. What does have to be implemented is a top level PVStructure and the following two methods:
Directory example/record has an example PVRecord implementation. It implements a counter. The top level structure is:
structure
long value
NOTE: The example compiles but does not build because nothing is implemented.
This is the class description. The example extends PVRecord.
class ExampleRecord :
public virtual PVRecord
{
public:
POINTER_DEFINITIONS(ExampleRecord);
static PVRecordPtr create(epics::pvData::String const & recordName);
virtual ~ExampleRecord();
virtual bool isSynchronous();
virtual void process(
epics::pvDatabase::RecordProcessRequesterPtr const &processRequester);
private:
ExampleRecord(epics::pvData::String const & recordName,
epics::pvData::PVStructurePtr const & pvStructure,
epics::pvData::PVLongPtr const &pvValue);
epics::pvData::PVLongPtr pvValue;
};
where
This is the class implementation.
ExampleRecord::~ExampleRecord(){}
PVRecordPtr ExampleRecord::create(String const & recordName)
{
String properties;
PVStructurePtr pvStructure = getStandardPVField()->scalar(pvLong,properties);
PVLongPtr pvValue = pvStructure->getLongField("value");
PVRecordPtr pvRecord(new ExampleRecord(recordName,pvStructure,pvValue));
return pvRecord;
}
ExampleRecord::ExampleRecord(
String const & recordName,
PVStructurePtr const & pvStructure,
PVLongPtr const &pvValue)
: PVRecord(recordName,pvStructure),
pvValue(pvValue)
{}
bool ExampleRecord::isSynchronous() {return true;}
void ExampleRecord::process(
RecordProcessRequesterPtr const &processRequester,bool alreadyLocked)
{
if(!alreadyLocked) lock();
pvValue->put(pvValue->get() + 1);
processRequester->recordProcessResult(Status::Ok);
unlock();
processRequester->recordProcessComplete();
dequeueProcessRequest(processRequester);
}
where
This is a main for creating and running the example.
int main(int argc,char *argv[])
{
String recordName("exampleRecord");
PVRecordPtr pvRecord = ExampleRecord::create(recordName);
PVDatabasePtr pvDatabase = PVDatabase::getMaster();
pvDatabase->addRecord(pvRecord);
cout << recordName << "\n";
string str;
while(true) {
cout << "Type exit to stop: \n";
getline(cin,str);
if(str.compare("exit")==0) break;
}
return 0;
}
The main program creates an example record and adds it to the database. It then runs until the process is stopped by typing exit.
Until the process is stopped, pvAccess clients can put and get the value field. For example
pvget exampleRecord pvput exampleRecord 5
Will both work.
This documentation describes the first phase of a phased implementation of pvDatabaseCPP:
Future phases of pvDatabaseCPP should 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 provide a first attempt to describe the classes required for the first phase.
The last section gives a brief overview of the features provided by pvIOCJava.
The classes in pvDatabase.h implement 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;
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 RecordProcessRequester;
typedef std::tr1::shared_ptr<RecordProcessRequester> RecordProcessRequesterPtr;
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);
PVRecord(
epics::pvData::String const & recordName,
epics::pvData::PVStructurePtr const & pvStructure);
virtual ~PVRecord();
virtual void process(
RecordProcessRequesterPtr const &recordProcessRequester,
bool alreadyLocked) = 0;
virtual bool isSynchronous() = 0;
virtual bool requestImmediatePut(epics::pvData::PVFieldPtr const &pvField);
virtual void immediatePutDone();
virtual void destroy();
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);
void lock();
void unlock();
bool tryLock();
void lockOtherRecord(PVRecordPtr const & otherRecord);
void addPVRecordClient(PVRecordClientPtr const & pvRecordClient);
void removePVRecordClient(PVRecordClientPtr const & pvRecordClient);
void detachClients();
void beginGroupPut();
void endGroupPut();
void queueProcessRequest(
RecordProcessRequesterPtr const &recordProcessRequester);
void dequeueProcessRequest(
RecordProcessRequesterPtr const &recordProcessRequester);
void queuePutRequest(
RecordPutRequesterPtr const &recordPutRequester);
void putDone(
RecordPutRequesterPtr const &recordPutRequester);
virtual epics::pvData::String getRequesterName();
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);
//init MUST be called after derived class is constructed
void init();
};
The methods are:
Derived classes must implement this method.
A client must only call this method when RecordProcessRequester::becomeProcessor is called as a result of a queueProcessRequest. A client can either call lock before calling processs or let process lock the record. If a client wants to put data into the record it should lock, put, and then call process.
If the record is synchronous, process will return only when all processing is complete. If the record is asynchronous then process arranges for some other thread to do the processing and returns.
When processing is done the record calls two client callbacks:
The purpose is to allow the implementation to provide fields that allow a client to abort process. For example a motor record might provide a field stop
The default always returns false.
A record implementation can override the default and return true. In it does requestImmediatePut it returns with the record locked.
The client can change the value of the associated field and then call immediatePutDone
The default does nothing.
Must be called by client as a result of a call to requestImmediatePut that returns true.
The default does nothing.
This is for code that wants to change data in a record without processing. If RecordPutRequester::requestResult is called with result true then the record is locked and the client can make changes. When done the client must call putDone
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();
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);
};
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();
PVRecordFieldPtrArrayPtr getPVRecordFields();
epics::pvData::PVStructurePtr getPVStructure();
virtual void removeListener(PVListenerPtr const & pvListener);
virtual void postPut();
};
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;
};
where
class RecordProcessRequester :
virtual public PVRecordClient,
virtual public epics::pvData::Requester
{
public:
POINTER_DEFINITIONS(RecordProcessRequester);
virtual ~RecordProcessRequester();
virtual void recordDestroyed() = 0;
virtual void becomeProcessor() = 0;
virtual void recordProcessResult(epics::pvData::Status status) = 0;
virtual void recordProcessComplete() = 0;
};
where
class RecordPutRequester :
virtual public PVRecordClient
{
public:
POINTER_DEFINITIONS(RecordPutRequester);
virtual ~RecordPutRequester();
virtual void requestResult(bool result) = 0;
};
where
class PVDatabase : virtual public epics::pvData::Requester {
public:
POINTER_DEFINITIONS(PVDatabase);
static PVDatabasePtr getMaster();
virtual ~PVDatabase();
PVRecordPtr findRecord(epics::pvData::String const& recordName);
bool addRecord(PVRecordPtr const & record);
bool removeRecord(PVRecordPtr const & record);
private:
PVDatabase();
};
where
Not yet described.
A brief description is that it must implement the following components of pvIOCJava:
The following are the direct sub packages of pvIOCJava/src/org/epics/pvioc:
In addition there is one class file JavaIOC.java. This is starting a IOC instance. This is not required for pvIOCCPP which is either a main or runs as part of a V3 IOC.