EPICS pvIOCCPP

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

Latest version:
pvIOCCPP.html
This version:
pvIOCCPP_20121116.html
Previous version:
pvIOCCPP_20121018.html
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:

pvData
pvData (Process Variable Data) defines and implements an efficent way to store, access, and communicate memory resident structured data
pvAccess
pvAccess is a software library for high speed controls network communications, optimized for pvData
pvIOC
pvIOC is a software framework for building network accessable "smart" real time databases, suitable for interfacing devices in a distributed control system, that can exchange pvData over pvAccess.
pvService
A middle layer for implementing efficient data services.

Each of these products has a Java and a C++ implementation.

The products are all part of the V4 implementation of Experimental Physics and Industrial Control System (EPICS).

Status of this Document

This is the 16-Nov-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.

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. For now this is the most important feature provided by this project so it will be the first topic discussed.
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 User's Guide

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.

It also provides the ability to get and put a set of V3 records. Only data from field VAL is allowed and all the V3 records must have the same scalar numeric type. The data appears as a scalarArray. The records can be processed. If all V3 records are in the same lockSet then each get or put is an atomic operation, i. e. dbScanLock is called only once. If the records are not in the same lockSet each record is individually locked.

Accessing a single V3Record

Because V3 records have a flat record structure the top level structure the pvAccess client sees also has a "flat" structure. The exception is that alarm, timeStamp, display, control, and valueAlarm appear as the pvData property structures. For example an ai record will appear as:

mrk> pvget -r "field()" double01
double01
uri:ev4:nt/2012/pwd:NTScalar 
    double value 0
    alarm_t alarm
        int severity 3
        int status 0
        string message UDF
    time_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 Counts
    control_t control
        double limitLow -0.1
        double limitHigh 9.9
        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
mrk> 

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

Accessing Multiple V3Records

The following are examples:

mrk> pvput -r "record[process=true]putField(syncputb,syncputc)" syncputa 3 1 2 3
Old : 
uri:ev4:nt/2012/pwd:NTScalarArray 
    double[] value [0,0,0]

[ChannelPutRequesterImpl] message(atomic true, info)
New : 
uri:ev4:nt/2012/pwd:NTScalarArray 
    double[] value [1,2,3]
mrk> pvget -r "record[process=true]getField(syncgetb,syncgetc)" syncgeta
[ChannelGetRequesterImpl] message(atomic true, info)
syncgeta
uri:ev4:nt/2012/pwd:NTScalarArray 
    double[] value [2,1,1]

Only get and put are supported. Only record names, i.e. no field name, can be given. All the V3 records must have the same scalar numeric scalar type. The channelName (in the above syncputa or syncgeta) is the first of the set of records desired. The putField for get or the getField for get is a list of the other records.

If process is requested then the semantics of V3 record processing must be kept in mind. Processing a record may cause the processing of linked records. If a record is an asynchronous record then the record may not complete processing until after the get or put completes. If the record is already active a new process request will not be made.

If an application wants a client to be able to do an atomic get or put to a set of record then a forward link record can be used to force the records into the same lockSet. For example:

record(calc, "syncgeta"){...}
record(calc, "syncgetb"){...}
record(calc, "syncgetc"){...}
record(fanout,"syncgetfanout")
{
        field(LNK1,"syncgeta")
        field(LNK2,"syncgetb")
        field(LNK3,"syncgetc")
}

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

Overview Of iocCore, pvAccess and pvIOC

Both iocCore and pvIOC are based on the concept of a database of memory resident "smart" records. A record is called smart because code is attached to the record and is triggered by asking the record to process.

CAV3 (channel access version 3), which is a component of iocCore, provides network access to iocCore records. pvAccess provides network access to pvIOC records. Both access a record via the channelName which is just the name of the record and optionally the name of a field in the record. The record name must be unique within the local area network.

This section provides an overview of:

channelAccess
Overview of the code that provides network access to data.
Record and Record Processing
Overview of how data in a record is structured and an overview of what happens when a record is processed.

Each of these is discused first for iocCore and then for pvAccess/pvIOC.

channelAccess

CAV3

For CAV3 a channelName consists of a "recordName.fieldName" The default fieldName is "VAL". The field data has one of the following types:

scalar
This is one of:
integer
Signed 8, 16, and 32 bit integers.
IEEE floating point
Single and Double precision
string
A 40 character C style string
array
An array of one of the scalar types. An array has fixed capacity but the length can vary.
enumerated
An index + a set of up to 16 26 character C style strings.

In addition a field can have some set of the following properties.

alarm
status and severity where each is a 16 bit signed integer. There is a header file and code for manipulating alarms.
timeStamp
An unsigned 32 bit integer that is the number of seconds since 1990 and an unsigned 32 bit integer that is nano seconds within the second. There is a header file and code for manipulating timeStamps.
display
display limits,precision, alarm limits, and units.
control
control limits

CAV3 provides I/O functions that falls into the following classes:

get
The client specifies the data type desired plus a set of property data. A fixed set of structures are defined for the property data. The IOC converts between the data type of the IOC record field and what the client requests.
put
The client specifies the data type desired.
monitor
The client specifies the data type desired plus a set of property data.

CAV3 provides no way for the client to influence record processing. Puts can cause processing but database access decides if a put causes a record to process.

pvAccess

For pvAccess a channelName consists of just the recordName. How the client specifies what data and options are desired are discussed below. For now just realize that each data item passed between client and server is a via an identical top level pvStructure on each side.

A brief description of a pvStructure is that it is a structure that has a set of PVFields where each field has one of the following types:

scalar
This is one of:
boolean
true or false
integer
Signed and unsigned 8, 16, 32 and 64 bit integers.
IEEE floating point
Single and Double precision
string
In Java a Java String. In C++ a std::string.
array
An array of one of the scalar types. Both the capacity and length can vary
structure
A substructure.
structureArray
An array of PVStructures where each element has the same introspection interface.

Although pvAccess itself does not get involved, pvData defines a set of "standard" PVStructures and assocated code called properties.

Note: pvData and pvAccess are not directly involved with normative types (NT). They merely make it possible to create NT structures.

The currently defined property structures are:

enumerated
A structure that has two fields: an integer index, and an array of strings named choices.
alarm
A structure that has fields status and severity where each is a 16 bit signed integer. In addition there is a string field named message. There are interface definitions and code for manipulating alarms.
timeStamp
An signed 64 bit integer that is the number of seconds since 1970 and a signed 32 bit integer that is nano seconds within the second. There are interface definitions and code for manipulating alarms.
display
display limits, description, format, and units.
valueAlarm
If the value field is type numeric then a structure with fields alarm limits, active, hystersis. If the value field is an enumerated structure then a structure with fields active, changeStateSeverity, and stateSeverity. The stateSeverity is an integer array.
control
control limits

pvAccess provides the following:

channelProcess
Ask for a record to process without any data transfer between client and server.
channelGet
Transfer data from server to client. The data is transfered between identical top level structures, which are created when the channelGet is created. Only data that has changed since the last get is tranfered.
channelPut
Transfer data from server to client. The data is transfered between identical top level structures, which are created when the channelPut is created. Only data that has changed since the last put is tranfered.
channelPutGet
This is a put followed by a get. Different structures are involved with put and get but again the structures at each end are created when the channelPutGet is created. Only data that has changed since the last putGet is tranfered.
channelArray
This supported put and get of subarray. The offset and lengthi are specified.
channelRPC
This is a put followed by get. No structures are created when the channelRPC is created. The structures for each request can be completely different than the previous request.
monitor
Ask to receive data from the server everytime the data changes subject to some algorithm. Each side can create a queue of structures for the data. Only data which changes is transfered. The client is notified of queue overflows.

When a client connects to a record the client receives an interface Channel. The interface has a create method for each of the above, i.e. createChannelProcess, etc. Each of these create methods has an argument "PVStructure pvRequest". The pvRequest structure is passed from the client to the server. pvAccess itself does not care what is in pvRequest; it is just a top level pvStructure. But a set of conventions exist for communication with a PVIOC. A pvAccess server should follow the same conventions unless it has special requirements.

There is a "convenience" service that, given a string, creates a pvRequest. A simple example is:

pvRequest = createRequest("field(alarm,timeStamp,value)");

The details are provided in pvAccessJava.html. The brief description is that the pvRequest allows the client to specify the following:

Data Desired
The client can select an arbitary subset of the fields in the pvIOC record. This applies to channelGet, channelPut, and channelMonitor. For channelPutGet the client can separately specify the put and get structures.
Record Options
An example is:
pvRequest = createRequest("record[process=true]field(alarm,timeStamp,value)");
This asks that the record be processed. There are two standard record options:
process
This can have the value true or false. This option applies to channelGet, channelPut, and channelPutGet. The default is false for get and true for channelPut and channelPutGet. For channelRPC there is no process option since proccess is implied.
queueSize
This is for monitor.
Field Options
Options can also be specified for individual fields. A standard option for scalar array fields is shareData. Monitor accepts options for monitor algorithms.

Record and Record Processing

V3IOC record data

A record is a flat structure, i.e. it is a structure that has a set of fields each of which has a name. Each field is one of the following types:

DBF_STRING,
DBF_CHAR,
DBF_UCHAR,
DBF_SHORT,
DBF_USHORT,
DBF_LONG,
DBF_ULONG,
DBF_FLOAT,
DBF_DOUBLE,
DBF_ENUM,
DBF_MENU,
DBF_DEVICE,
DBF_INLINK,
DBF_OUTLINK,
DBF_FWDLINK,
DBF_NOACCESS

DBF_CHAR, ... ,DBF_DOUBLE are the numeric types. The other types are:

DBF_STRING
This is a string field. A string field has a capacity determined when by how the field is created. The actual data is a C char array. It can have any size which can be determined via facilities provided by dbAccess.
DBF_ENUM
This is a for the VAL field. The field is an unsigned 16 bit integer. The Record Support Entry Table (rset discussed below) provides a method get_enum_strs which provides the set of choices. The bi, bo, mbbi, mbbo, etc record types inplement the choices via a separate DBF_STRING field for each choice. A client can only change a choice by knowing details about the record type.
DBF_MENU
The V3 database supports this type. It makes the data available to clients as a enumerated value. The choices are immutable.
DBF_DEVICE
This is the type for field DTYP, which is a field common to all record types. It specifies what type of device support is attached to the record. It is also made available to clients as an enumerated value. The choices are determined when the record instance is created and can not be changed dynamically.
DBF_INLINK
DBF_OUTLINK
These are string fields used by device support. The format is determined by the device support. Some types of device support can be changed dynamically but the client must know the syntax
DBF_FWDLINK
This is a string field that can hold the name of another record. If configured then when the record holding the link is processed the linked record is also processed.
DBF_NOACCESS
This is a field that hold private data that can not be accesed from outside the record.

V3IOC record processing

A record instance has a record type. Each record type has a fixed set of fields. Each has a common set of 43 fields and a set of record type specific fields. For example ai adds 39 fields and mbbi adds 65 fields. Most of these fields are not of interest to clients. The field of most interest is VAL. Many other fields are properties of the value field. Normally client code does not directly access property fields but gets their values via chnnelAccess. Many fields are private and can not be accessed via channelAccess.

EPICS base provides a set of record types, e. g. ai, ao, bi, bo, etc. It also provides facilities for defining additional record types. Each record type has associated code that is accessed via a rset (Record Support Entry Table):

typedef struct rset {   /* record support entry table */
        long            number;         /*number of support routines    */
        RECSUPFUN       report;         /*print report                  */
        RECSUPFUN       init;           /*init support                  */
        RECSUPFUN       init_record;    /*init record                   */
        RECSUPFUN       process;        /*process record                */
        RECSUPFUN       special;        /*special processing            */
        RECSUPFUN       get_value;      /*get value field               */
        RECSUPFUN       cvt_dbaddr;     /*cvt  dbAddr                   */
        RECSUPFUN       get_array_info;
        RECSUPFUN       put_array_info;
        RECSUPFUN       get_units;
        RECSUPFUN       get_precision;
        RECSUPFUN       get_enum_str;   /*get string from enum item*/
        RECSUPFUN       get_enum_strs;  /*get all enum strings          */
        RECSUPFUN       put_enum_str;   /*put string from enum item*/
        RECSUPFUN       get_graphic_double;
        RECSUPFUN       get_control_double;
        RECSUPFUN       get_alarm_double;
}rset;

These method are called by dbAccess, which is major component of iocCore. Method process is called to process the record. The details are determined by the record suport. The methods get_value, ... ,get_alarm_double are all related to the field named VAL. get_array_info and put_array_info apply if the value field is an array. get_enum_str ,..., put_enum_str apply if value is an enumerated field. The other get_xxx methods get properties of the value field.

Three fields from dbCommon releted to record processing are SCAN,FLNK, and DTYP. Many record types have either a field INPUT or OUTPUT.

SCAN
This is a menu field that determines what causes the record to process. A record can be processed periodically or by two different interrupt mechanism. A record can also be declared passive, which means that it is only processed when a client or a link from another record asks that the record be processed. A field can have an attribute of process passive. When a client issues a put to the field and SCAN is passive then the record is processed.
FLNK
This is a forward link. The link can either be a dblink or a calink. For a dblink the linked record will be part of the same scan set as the record containing the FLNK. The concept of scan set is discussed below. This field is handled by dbAccess rather than the record support.

DTYP
A record type can have associated device support. Device support is called by record support via a dset (device support entry table. The device support can be anything that implements the dset. Often the device support accesses hardware. A common example is support that communicates with the asynDriver facility. But iocCore provides support for links the other records (dblink) and to CAV3 channels (calink). Like FLINK the dblink record will be in the same lock set.
INPUT
OUTPUT
If the record type has an INPUT field then, by definition, it is an input record, i. e. it gets data from somewhere else. Similarly if it has an OUTPUT field it is an output record. In either case this field is a string field that has configuration information for the device support. The format is determined by the device support.

Because processing a record can cause linked records to also be processed, the concept of scan set exists. Before a record is processed a function dbScanLock is called and when it completes processing dbScanUnlock is called. All records linked together directly or indirectly are placed in the same lock set. When dbScanLock is called the entire set, not just the specified record, is locked. This prevents two different tasks from simultaneously modifying records in the same lock set.

Device support can be either synchronous or asynchronous. A synchronous record is a record where device support can complete without wait for I/O. Asynchronous support is support that needs to wait. Asynchonous processing consists of two two phases: process start and process completion. Between the two phases the record is unlocked.

pvIOC record data

NOTE: currently record and record processing is only impelemented by pvIOCJava.

A PVRecord has a top level PVStructure. Thus the data layout is just PVData. As mentioned above pvData defines standard structures and support for enumerated, alarm, timeStamp, display, control, and valueAlarm.

pvIOC record processing

pvIOC has the the same processing features as V3IOC. The main difference are:

locking
The record is locked. There is the ability to lock one other record but no concept of scan sets. The concept of synchronous and asynchronous does exists.
support
No concept of record types, record support, and device support exists. Instead support can optionally be attached to any field. Support must implement the methods defined by interface Support When support is initialized it looks, starting with the field to which it is attached, for fields that it requires. Several basic support modules are provided by pvIOC. Amoung these is Generic. It is normally attached to the top level structure. It looks in the immediate substructure for each field that have attached support and calls it's support methods
support configuration
Configuration is done via PVData fields

The interface for support is:

interface Support {
    void initialize();
    void start(AfterStart afterStart);
    void stop();
    void uninitialize();
    void process(SupportProcessRequester supportProcessRequester);
}

Although record types do not exist there are standards for field layout so that the equivalent of the V3IOC ai, ao, etc records can be created.

Support is provided that implements database and channelAccess links.

CAV3/V3Record <==> pvIOC/pvAccess

The status of interoperability between V3 and pvIOC/pvAccess is:

pvAccessJava
A pvAccess can comunicate with a channel via either CAV3 of pvAccess. In either case the data appears to the client as pvData.
pvIOCJava
This provides a CAV3 server. This allows a CAV3 client to access fields of a PVRecord.
pvAccessCPP
Currenly has no support for CAV3.
pcIOCCPP
v3Channel is a pvAccess server for accessing V3 records.

From the point of view of a pvAccess client.

Java Client
Has access to a channel using either the CAV3 or pvAccess network protocal. Thus it has access to PVRecords on a pvIOCJava and to V3 records on any V3 IOC. It also has access to multiChannal data that resides in a pvIOCCPP.
C++ client
Has access to a channel only via pvAccess protocal. Thus it has access to PVRecords on a pvIOCJava and has access to V3 data only if the V3IOC has the v3Channel server running. It also has access to multiChannal data that resides in a pvIOCCPP.

A CAV3 client has access to a channel that is a PVRecord only if the record is on a pvIOCJava.

Future Plans For pvIOCCPP

Introduction

What is currently implemented by pvIOCCPP has almost non of the features a pvIOC is expected to provide. The documentation for pvIOCJava descibes what a full implementation provides.

Other then service and ezchannelRPC, which should be moved to pvAccessCPP, the only things pvIOCCPP currently provides 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 take a lot of code unless pvIOC provides help.

At present the only Channel implementations in pvIOCCPP are v3Channel and multiChannel.

v3Channel implements a pvAccess server for accessing V3 records. It implements everything that makes sense for V3 records.

multiChannel implements Channel and ChannelGet. Each instance provides a channel that holds data from a set of channels obtained from another channelProvider. 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 following discusses a phased implementation to a full implementation of what a pvIOC should provide:

Minimum Features For Channel

The minimum features will allow a full implementation of Channel but will require code for each type of functionality desired. In addition there will only be a single support for a record instance.

With the minimum features a complete Channel implementation for multiChannel is possible.

The minimum set of features is:

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.
PVRecord
The features of PVRecord as defined by pvIOCJava with the support methods replaced by just method process.
localChannelProvider
localChannelProvider will access data from records using the features above. It will implement all channel methods except channelRPC.

Add Support

This phase adds the ability to optionally add support to any field of a record. This phase includes the set of basic support that pvIOCJava implements. This phase still requires code to create each record and register it with channelProvider.

This phase requires:

Support Interface
See pvIOCJava for definition.
Basic support
See pvIOCJava for definition.
other useful support
Other support can be added. Looking at what pvIOVJava provides is a good starting place.

Add support for using asynDriver

This provides access to all the support provided by asynDriver which is huge.

Add database and install and scanning

This provides a database and on-line add and remove records. It still requires code to create record instances.

An xml parser

This provides the ability to create records without writing code.

Implement portDriver

This is a replacement for the asynManger component of asynDriver. See the implementation in pvIOCJava for details.

v3Channel 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);
    static V3ChannelProviderPtr getV3ChannelProvider();
    virtual ~V3ChannelProvider();
    virtual ChannelFind::shared_pointer channelFind(
        String const &channelName,
        ChannelFindRequester::shared_pointer const & channelFindRequester);
    virtual Channel::shared_pointer createChannel(
        String const &channelName,
        ChannelRequester::shared_pointer  const &channelRequester,
        short priority,
        String const &address);
private:
}

where

getV3ChannelProvider
This returns the single instance od the V3ChannelProvider.
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(
        gChannelBaseProvider::shared_pointer const & channelProvider,
        gChannelRequester::shared_pointer const & requester,
        gString const & name,
        std::auto_ptr addr
        );
    virtual ~V3Channel();
    void init();
    virtual void getField(
        gGetFieldRequester::shared_pointer const &requester,
        gString const &subField);
    virtual gChannelProcess::shared_pointer createChannelProcess(
        gChannelProcessRequester::shared_pointer const &channelProcessRequester,
        gPVStructure::shared_pointer const &pvRequest);
    virtual gChannelGet::shared_pointer createChannelGet(
        gChannelGetRequester::shared_pointer const &channelGetRequester,
        gPVStructure::shared_pointer const &pvRequest);
    virtual gChannelPut::shared_pointer createChannelPut(
        gChannelPutRequester::shared_pointer const &channelPutRequester,
        gPVStructure::shared_pointer const &pvRequest);
    virtual gMonitor::shared_pointer createMonitor(
        gMonitorRequester::shared_pointer const &monitorRequester,
        gPVStructure::shared_pointer const &pvRequest);
    virtual gChannelArray::shared_pointer createChannelArray(
        gChannelArrayRequester::shared_pointer const &channelArrayRequester,
        gPVStructure::shared_pointer const &pvRequest);
    virtual void printInfo();
    virtual void printInfo(StringBuilder out);
private:
...
}

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:
    POINTER_DEFINITIONS(V3Util);
    virtual ~V3Util() {}
    static V3UtilPtr getV3Util();
    // client request bits
    int processBit;       // is processing requested
    int shareArrayBit;    // share V3array instead of copy
    int timeStampBit;     // get timeStamp;
    int alarmBit;         // get alarm
    int displayBit;       // get display info
    int controlBit;       // get control info
    int valueAlarmBit;    // get value alarm info
    // V3 data characteristics
    int scalarValueBit;   // value is a scalar
    int arrayValueBit;    // value is an array
    int enumValueBit;     // value is an enum
    int noAccessBit;      // fields can not be accessed
    int noModBit;         // fields can not be modified
    int dbPutBit;         // Must call dbPutField
    int isLinkBit;        // field is a DBF_XXLINK field

    int getProperties(
        gRequester::shared_pointer const &requester,
        gPVStructure::shared_pointer const &pvRequest,
        DbAddr &dbAddr,
        bool processDefault);
    gPVStructurePtr createPVStructure(
        gRequester::shared_pointer const &requester,
        int mask,DbAddr &dbAddr);
    void getPropertyData(
        gRequester::shared_pointer const &requester,
        int mask,DbAddr &dbAddr,
        gPVStructurePtr const &pvStructure);
    gStatus get(
        gRequester::shared_pointer const &requester,
        int mask,DbAddr &dbAddr,
        gPVStructurePtr const &pvStructure,
        gBitSet::shared_pointer const &bitSet,
        CAV3Data *caV3Data);
    gStatus put(
        gRequester::shared_pointer const &requester,
        int mask,DbAddr &dbAddr,
        gPVFieldPtr const &pvField);
    gStatus putField(
        gRequester::shared_pointer const &requester,
        int mask,DbAddr &dbAddr,
        gPVFieldPtr const &pvField);
    gScalarType getScalarType(
        gRequester::shared_pointer const &requester,
        DbAddr &dbAddr);
private:
...
}

where

getV3Util
Gets the single instance of V3Util
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);
    gPVByteArrayPtr createByteArray(
        gPVStructurePtr const & parent,
        gScalarArrayConstPtr const & scalar,
        DbAddr &dbAddr,bool shareData);
    gPVUByteArrayPtr createUByteArray(
        gPVStructurePtr const & parent,
        gScalarArrayConstPtr const & scalar,
        DbAddr &dbAddr,bool shareData);
    gPVShortArrayPtr createShortArray(
        gPVStructurePtr const & parent,
        gScalarArrayConstPtr const & scalar,
        DbAddr &dbAddr,bool shareData);
    gPVUShortArrayPtr createUShortArray(
        gPVStructurePtr const & parent,
        gScalarArrayConstPtr const & scalar,
        DbAddr &dbAddr,bool shareData);
    gPVIntArrayPtr createIntArray(
        gPVStructurePtr const & parent,
        gScalarArrayConstPtr const & scalar,
        DbAddr &dbAddr,bool shareData);
    gPVUIntArrayPtr createUIntArray(
        gPVStructurePtr const & parent,
        gScalarArrayConstPtr const & scalar,
        DbAddr &dbAddr,bool shareData);
    gPVFloatArrayPtr createFloatArray(
        gPVStructurePtr const & parent,
        gScalarArrayConstPtr const & scalar,
        DbAddr &dbAddr,bool shareData);
    gPVDoubleArrayPtr createDoubleArray(
        gPVStructurePtr const & parent,
        gScalarArrayConstPtr const & scalar,
        DbAddr &dbAddr,bool shareData);
    // Note that V3StringArray can not share
    gPVStringArrayPtr createStringArray(
        gPVStructurePtr const & parent,
        gScalarArrayConstPtr const & scalar,
        DbAddr &dbAddr);
};

typedef std::tr1::shared_ptr 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
v3UByte
The monitor request will be a DBR_TIME_CHAR.
v3Short
v3UShort
The monitor request will be a DBR_TIME_SHORT.
v3Int
v3UInt
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
        int8   byteValue;
        uint8  ubyteValue;
        uint16 ushortValue;
        int16  shortValue;
        int32  intValue;
        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 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:
...
}

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

:1,$s/

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

CAV3Context is defined as:

class CAV3Context {
public:
    POINTER_DEFINITIONS(CAV3Context);
    ~CAV3Context();
    void release();
    void stop();
    void exception(String const &message);
    void checkContext();
private:
...
};

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 CAV3Context 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:

lass CAV3ContextCreate {
public:
    static CAV3ContextPtr get(epics::pvData::RequesterPtr const & requester);
private:
...
}

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 std::tr1::enable_shared_from_this
{
public:
    POINTER_DEFINITIONS(ChannelBaseProvider);
    ChannelBaseProvider(
        String const &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;
    // 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 const &channel);
    void removeChannel(ChannelBase::shared_pointer const &channel);
    void unregisterSelf();
    void registerSelf();
protected:
...
}

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