pvDatabaseCPP

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

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

Abstract

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:
relatedDocumentsV4.html

Status of this Document

This is the 27-Nov-2012 version of the definition of pvDatabaseCPP. This is the original version.

This is the beginning of the implementation of pvDataBaseCPP. It describes the features that will be provided. The class definitions for PVRecord and PVDatabase are defined but not implemented.

Table of Contents

Introduction

Overview

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
This encapsulates the concept of a database of memory resident smart records. The two main components are:
pvRecord
This encapsulates the concept of a smart record. It can be processed. Changes to field values can be trapped. A record can be locked.
pvDatabase
This is a database of pvRecords. Records can be added and removed from a database.
localChannelProvider
This is a complete implementation of ChannelProvider and Channel as defined by pvAccess. It is used by the server side of pvAccess to attach to pvRecords. This component also includes the monitor and pvCopy components from pvIOCJava

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:

process
This is what makes a record smart. What process does is up to the implementation except that it must decide if it's execution model is synchronous or asynchronous. Synchronous means that when process returns the processing is complete. Asynchronous means that when process returns the processing is not complete. Instead process invokes other threads that will complete the processing at a later time.
isSynchronous
Which execution model is being implemented.

Example PVRecord Extension

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.

exampleRecord.h

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

create
This is example specific. See the implemention for details.
~ExampleRecord
The destructor must be declared virtual.
isSynchronous
The implementation must say if process is synchronous or asynchronous.
process
The implementation.
ExampleRecord
For the example this is private.

exampleRecord.cpp

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)
{
    pvValue->put(pvValue->get() + 1);
    processRequester->recordProcessResult(Status::Ok);
    processRequester->recordProcessComplete();
}

where

create
Creates a PVStructure with a single subfield named value. It gets the interface to the value field. It then creates an ExampleRecord and returns it.
~ExampleRecord
Does not have to do anything because of shared pointers.
ExampleRecord
Calls the base class constructor and sets pvValue.
isSynchronous
The example is synchronous.
process
Gets the curent value, increments it, and puts the new value. It than calls two processRequester callbacks.

exampleRecordMain.cpp

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.

Phased Development

This documentation describes the first phase of a phased implementation of pvDatabaseCPP:

pvRecord
Wrapper on PVStructure that implements methods required by Local Channel Provider.
pvDatabase
Database of PVRecords. Has methods find, add, and remove.
Local Channel Provider
These two features will be the first phase. But only synchronous record processing will be supported.

Future phases of pvDatabaseCPP should include:

Install
This provides on-line add and delete.
Field support
Add ability to optionally add support to fields. In addition some of the basic support defined in pvIOCJava will also be implemented.
XML parser
This provides the ability to create record instances without writing any code.

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.

Features Required for localChannelProvider

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 to fields of a record.
PVRecord and PVDatabase
Defined below.
local ChannelProvider
This is the pvAccess package in pvIOCJava. The localChannelProvider will access data from PVRecords. It will implement all channel methods except channelRPC.

Minumum Features Required for pvRecord

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

PVDatabase
This holds a set of PVRecords. It has methods to find, add, and remove records.
PVRecord
This, and a set of related interfaces, provide the following:
PVStructure
PVRecord is a wrapper on a top level pvStructure.
Record locking
A record can be locked and unlocked. A record must be locked whenever data in the pvStructure is accessed.
Trapping data changes
A client can request to be notified when data in the pvStructure is modified. It can do this on a field by field basis.

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.

database

The classes in pvDatabase.h implement a database of memory resident smart records. The next subsection has the definitions for all the classes defined in this header file. It describes the following classes:

PVRecord
This provides the methods required by localChannelProvider to implement Channel.
PVRecordField
PVRecordStructure
These wrap PVField and PVStructure so that pvCopy and monitor can be implemented.
PVListener
This is implemented by anything that wants to trap calls to the PVRecord::message.
RecordProcessRequester
This is implemented by anything that calls PVRecord::queueProcessRequest.
PVRecordClient
This is called by anything that acceses PVRecord.
PVDatabase
This is a database of PVRecords.

Each class is described in a separate subsection.

class PVRecord

class PVRecord
{
public:
    POINTER_DEFINITIONS(PVRecord);
    PVRecord(
        epics::pvData::String const & recordName,
        epics::pvData::PVStructurePtr const & pvStructure);
    virtual ~PVRecord();
    virtual void process(
        RecordProcessRequesterPtr const &recordProcessRequester) = 0;
    virtual bool isSynchronous() = 0;
    epics::pvData::String getRecordName();
    PVRecordStructurePtr getPVRecordStructure();
    PVRecordFieldPtr findPVRecordField(
        epics::pvData::PVFieldPtr const & pvField);
    void lock();
    void unlock();
    void registerClient(PVRecordClientPtr const & pvRecordClient);
    void unregisterClient(PVRecordClientPtr const & pvRecordClient);
    void detachClients();
    void beginGroupPut();
    void endGroupPut();
    void registerListener(PVListenerPtr const & pvListener);
    void unregisterListener(PVListenerPtr const & pvListener);
    void removeEveryListener();
    epics::pvData::Status processRequest();
    void queueProcessRequest(
        RecordProcessRequesterPtr const &recordProcessRequester);
    void addRequester(epics::pvData::RequesterPtr const & requester);
    void removeRequester(epics::pvData::RequesterPtr const & requester);
    void message(
        epics::pvData::String const & message,
        epics::pvData::MessageType messageType);
    epics::pvData::String toString();
    epics::pvData::String toString(int indentLevel);
};

The methods are:

PVRecord
The constructor. It requires a recordName and a top level PVStructure.
~PVRecord
The desctructor which must be virtual. A derived class must also have a virtual destructor.
process
Pure virtual method. Derived classes must implement this method.
isSynchronous
Pure virtual method. Derived classes must implement this method.
getRecordName
Return the recordName.
getPVRecordStructure
Get the top level PVStructure.
findPVRecordField
Given a PVFieldPtr return the PVRecordFieldPtr for the field.
lock
unlock
Lock and Unlock the record. Any code accessing the data in the record or calling other PVRecord methods must have the record locked.
registerClient
Every client that accesses the record must call this so that the client can be notified when the record is deleted.
unregisterClient
Client is no longer accessing the record.
detachClients
Ask all clients to detach from the record
beginGroupPut
Begin a group of puts. This results in all registered PVListeners being called
endGroupPut
End a group of puts. This results in all registered PVListeners being called.
registerListener
Register a PVListener. This must be called before calling pvRecordField.addListener.
unregisterListener
Unregister a listener. The listener will also be removed from all fields to which it is attached.
removeEveryListener
This must be called by any code that is deleting or changing the structure of a record.
processRequest
This is a convenience method for clients that are willing to block if process is asynchronous. It implements RecordProcessRequester. If process is synchronous it just calls process and returns the result to the caller. If process is asynchronous it calls queueProcessRequest, and process and waits for completion and then returns the result to the caller.
queueProcessRequest
Queue a process request.
addRequester
Add a requester to receive messages.
removeRequester
Remove a message requester.
message
Can be called by implementation code. The message will be sent to every requester.

class PVRecordField

class PVRecordField {
public:
    POINTER_DEFINITIONS(PVRecordField);
    PVRecordField(
        epics::pvData::PVFieldPtr const & pvField,
        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);
    void removeListener(PVListenerPtr const & pvListener);
    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:

PVRecordField
The constructor.
~PVRecordField
The destructor.
getParent
Get the parent PVRecordStructure for this field.
getPVField
Get the PVField associated with this PVRecordField.
getFullFieldName
This gets the full name of the field, i.e. field,field,..
getFullName
This gets recordName plus the full name of the field, i.e. recordName.field,field,..
getPVRecord
Returns the PVRecord to which this field belongs.
addListener
Add A PVListener to this field. Whenever this field or any subfield if this field is modified the listener will be notified. PVListener is described below. Before a listener can call addListener it must first call PVRecord.registerListener.
removeListener
Remove a PVListener.
postPut
This is called by the code that implements the data interface. It is called whenever the put method is called.
message
Called by implementation code. It calls PVRecord::message after prepending the full fieldname.

class PVRecordStructure

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 message(
        epics::pvData::String const & message,
        epics::pvData::MessageType messageType);
};

When PVRecord is created it creates a PVRecordStructure for every structure field in the PVStructure that holds the data. It has the following methods:

PVRecordStructure
The constructor.
~PVRecordStructure
The destructor.
getPVRecordFields
Get the PVRecordField array for the subfields
getPVStructure
Get the PVStructure for this field.
message
Called by implementation code. It calls PVRecord::message after prepending the full fieldname.

class PVListener

class PVListener {
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) = 0;
};

where

~PVListener
The destructor.
dataPut(PVRecordFieldPtr const & pvRecordField)
pvField has been modified. This is called if the listener has called PVRecordField::addListener for pvRecordField.
dataPut( PVRecordStructurePtr const & requested,PVRecordFieldPtr const & pvRecordField)
pvField has been modified. Requested is the field to which the requester issued a pvField-&addListener. This is called if the listener has called PVRecordField-&addListener for requested.
beginGroupPut
A related set of changes is being started.
endGroupPut
A related set of changes is done.
unlisten
The PVLister is being removed from the record. This is called when the record is being destroyed or when the record structure (not the data values) is being changed.

class RecordProcessRequester

class RecordProcessRequester :
    virtual public epics::pvData::Requester
{
public:
    POINTER_DEFINITIONS(RecordProcessRequester);
    virtual ~RecordProcessRequester();
    virtual void becomeProcessor() = 0;
    virtual void recordProcessResult(epics::pvData::Status status) = 0;
    virtual void recordProcessComplete() = 0;
};

where

~RecordProcessRequester
The destructor.
becomeProcessor
Called as a result of queueRequeProcessst. The requester can the call process.
recordProcessResult
The results of record processing. This is called with the record locked so that the process requester can access data from the record.
recordProcessComplete
Processing is complete. This is called with the record unlocked. If the process requester called process with leaveActive true then the requester must call setInactive.

class PVRecordClient

class PVRecordClient {
    POINTER_DEFINITIONS(PVRecordClient);
    virtual ~PVRecordClient();
    virtual void detach(PVRecordPtr const & pvRecord);
};

where

~PVRecordClient
The destructor.
detach
The record is being removed from the master database,

class PVDatabase

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

getMaster
Get the master database. This is the database that localChannelProvider access.
~PVDatabase
The destructor.
findRecord
Find a record. An empty pointer is returned if the record is not in the database.
addRecord
Add a record to the database. If the record already exists it is not modified and false is returned.
removeRecord
Remove a record from the database. If the record was not in the database false is returned.

Local Channel Provider

Not yet described.

A brief description is that it must implement the following components of pvIOCJava:

pvCopy
monitor
pvAccess
See the next section for a description

Summary of Packages in pvIOCJAVA

The following are the direct sub packages of pvIOCJava/src/org/epics/pvioc:

pvCopy
This provides a copy of an arbitrary subset of the fields in a PVRecord. It also provides the ability to detect and report changes to fields. It is required for pvAccess.
monitor
This provides the ability to monitor changes to a PVRecord. It is required for pvAccess monitors.
pvAccess
The local implementation of Channel Provider and Channel. It is accessed by the remote pvAccess server and can also be accessed by code in the same IOC.
database
This defines and implements PVRecord, PVDatabase , and PVListener. It supports the basic feature required the implement a local Channel Provider.
support
This provides the ability to optionally attach code to any field of a pvRecord. It and several sub packages provide a set of standard support modules.
install
This provides the ability to dynamically initialize and add new PVRecords. It also provides the ability to dynamicall delete PVRecords.
xml
This provides the ability to configure record instances without writing code.
util
This is misnamed since it is code related to scanning.
pdrv
This is portDriver, which is a proposed sucessor to the asynManager component of asynDriver.
swtshell
This is shell that is can either run under the same process as a JavaIOC or as a remote shell. It is like a version of probe but for pvData/pvAccess. Almost all of it's features work in either local or remote mode. With a little more work all or it's features could work remotely. This should be done and then only remote mode should be supported. It can then be rewritten in a completely different language and using a complely different GUI framework.
caV3
This has two components:
ClientFactory
This is a small wrapper on top of the caV3 client support implemented by pvAccess. It allows code in the pvIOC to access V3Records via pvAccess.
ServerFactory
This is a caV3 server that allows a caV3 client to access a PVRecord. The Java implementation uses CAJ, which does most of the work. For now it will not be discussed in this document.
v3a
I do not know what this is.

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.