pvDatabase Tutorial

2019.07.03

Editors:
Marty Kraimer

WARNING This tutorial requires recent versions of pvDatabaseCPP and exampleCPP that are not in any official release as of EPICS 7.0.2. Until a later release is available, code must be cloned from "https://github.com/epics-base"

This product is available via an open source license

Table of Contents

Overview

This tutorial has two main objectives:

pvDatabase features
Shows various features including pluginSupport, recordSupport, and specialRecords
Implementing a PVRecord
Explains how the support example is organized. It can be used as a model for implementing services based on PVRecords.

This tutorial assumes that you are somewhat familar with the following:

developerGuide

developerGuide Is an introducion to the various components of EPICS V4

exampleCPP

exampleCPP Is a set of EPICS V4 examples. This tutorial is based on example support.

pvDatabaseCPP

pvDatabaseCPP This tutorial is an introduction to pvDatabaseCPP.

building

While reading this tutorial you are encouraged to try the examples, which requires that you have built pvDatabaseCPP and exampleCPP.

This tutorial discusses the following components of pvDatabaseCPP.

  1. pluginSupport
  2. recordSupport
  3. specialRecord

Starting The example

Start database

After building exampleCPP, start the database for the support examples as follows:

mrk> pwd
/home/epicsv4/masterCPP/exampleCPP/support/iocBoot/support
mrk> ../../bin/linux-x86_64/support st.cmd 
...
traceRecordCreate PVRtraceRecord
removeRecordCreate PVRremoveRecord
processRecordCreate PVRprocessRecord .5
supportRecordCreate PVRsupportDouble
supportRecordCreate PVRsupportUByte pvUByte
scalarArrayRecordCreate PVRscalarArrayDouble pvDouble
scalarArrayRecordCreate PVRscalarArrayUByte pvUByte
scalarArrayRecordCreate PVRscalarArrayString pvString
scalarArrayRecordCreate PVRscalarArrayBoolean pvBoolean
scalarRecordCreate PVRscalarDouble pvDouble -10 10 .5
scalarRecordCreate PVRscalarUByte pvUByte 0 20 1
epics> 
The following commands are used to create PVRecords:

traceRecordCreate

Support for this record is implemented by pvDatabase.

The created record sets the trace level in another pvRecord in the local pvDatabase. The trace level determines what messages are displayed when clients access the pvRecord. If the level is 0 no messages are displayed. If the level is 1 then connection and creation messages are displayed. If the level is 2 then messages are displayed for all client interaction.

A created record has the following structure:

mrk> pvinfo PVRtraceRecord
PVRtraceRecord
Server: 10.0.0.9:5075
Type:
    structure
        structure argument
            string recordName
            int level
        structure result
            string status

An example of using the record is:

mrk> pvput -r "argument,result" PVRtraceRecord argument='{"recordName":"PVRscalarDouble","level":"2"}'
Old : structure 
    structure argument
        string recordName 
        int level 0
    structure result
        string status 
New : structure 
    structure argument
        string recordName PVRscalarDouble
        int level 2
    structure result
        string status success
This sets the trace level to 2 in record PVRscalarDouble.

This record is not used in rest of this tutorial.

removeRecordCreate

Support for this record is implemented by pvDatabase.

The created record removes another pvRecord in the local pvDatabase.

A created record that has the following structure:

mrk> pvinfo PVRremoveRecord
PVRremoveRecord
Server: 10.0.0.9:5075
Type:
    structure
        structure argument
            string recordName
        structure result
            string status

An example of using the record is:

mrk> pvput -r "argument,result" PVRremoveRecord argument='{"recordName":"PVRscalarDouble"}'
Old : structure 
    structure argument
        string recordName 
    structure result
        string status 
New : structure 
    structure argument
        string recordName PVRscalarDouble
    structure result
        string status success

This removes record PVRscalarDouble from the local pvDatabase.

This record is not used in rest of this tutorial.

processRecordCreate

Support for this record is implemented by pvDatabase.

The created record creates a thread that processes other records in the local pvDatabase. Before each round of processing it delays for the numbeSupport for this record is implemented by pvDatabase. The record supports two commands: add and remove.

A created record has the following structure:

mrk> pvinfo PVRprocessRecord
PVRprocessRecord
Server: 10.0.0.9:5075
Type:
    structure
        structure argument
            string command
            string recordName
        structure result
            string status

An example of using the record is:

mrk> pvput -r "argument,result" PVRprocessRecord argument='{"command":"add","recordName":"PVRscalarDouble"}'
Old : structure 
    structure argument
        string command 
        string recordName 
    structure result
        string status 
New : structure 
    structure argument
        string command add
        string recordName PVRscalarDouble
    structure result
        string status PVRscalarDouble not in pvDatabase
This adds PVRscalarDouble to the set of records to process. Note that the request failed because the previous pvput removed PVRscalarDouble.

This record is used in the rest of this tutorial.

supportRecordCreate

Support for this record is provided in exampleCPP/support. It is used and described later in the tutorial.

The created record uses the control and scalarAlarm support from pvDatabase.

A created record has the following structure:

mrk> pvinfo PVRsupportDouble
PVRsupportDouble
Server: 10.0.0.9:5075
Type:
    structure
        double value
        boolean reset
        alarm_t alarm
            int severity
            int status
            string message
        time_t timeStamp
            long secondsPastEpoch
            int nanoseconds
            int userTag
        display_t display
            double limitLow
            double limitHigh
            string description
            string format
            string units
        control_t control
            double limitLow
            double limitHigh
            double minStep
            double outputValue
        scalarAlarm_t scalarAlarm
            double lowAlarmLimit
            double lowWarningLimit
            double highWarningLimit
            double highAlarmLimit
            double hysteresis
example usage appears later in this tutorial.

scalarArrayRecordCreate

Support for this record is provided in exampleCPP/support. It creates a record that has value field, which has type scalar array, and a timeStamp field. It is used to the demonstrate use of the array plugin.

A created record has the following structure:

mrk> pvinfo PVRscalarArrayDouble
PVRscalarArrayDouble
Server: 10.0.0.9:5075
Type:
    structure
        double[] value 
        time_t timeStamp
            long secondsPastEpoch
            int nanoseconds
            int userTag

NOTE: The type can be any scalar type, i.e. boolean,byte,short,int,long,ubyte,ushort,uint,ulong,float,double,string.

scalarRecordCreate

Support for this record is provided in exampleCPP/support. It creates a record that has value field, which has type scalar, and a timeStamp field. The process routine generates a sawtooth. It is described later in this tutorial.

A created record has the following structure:

mrk> pvinfo PVRscalarDouble
PVRscalarDouble
Server: 10.0.0.9:5075
Type:
    structure
        double value
        time_t timeStamp
            long secondsPastEpoch
            int nanoseconds
            int userTag

NOTE: The type can be any numeric scalar type, i.e. byte,short,int,long,ubyte,ushort,uint,ulong, float, or double.

Use clients to configure records

Run the following:

mrk> pwd
/home/epicsv4/masterCPP/exampleCPP/support/client/scripts
mrk> ./configAll

configAll does the following:

Run an example client

Look at the following

mrk> pvget -r "control,scalarAlarm" PVRsupportDouble
PVRsupportDouble structure 
    control_t control
        double limitLow -10
        double limitHigh 10
        double minStep 0.5
        double outputValue 10
    scalarAlarm_t scalarAlarm
        double lowAlarmLimit -8
        double lowWarningLimit -6
        double highWarningLimit 6
        double highAlarmLimit 8
        double hysteresis 0.1

In one window enter

pvget -m -r "value,control.outputValue,alarm,timeStamp" -v PVRsupportDouble
The output from this window will be described after you enter the next command.

Enter:

pvput PVRsupportDouble  20

Results

In the window where you issued pvget you will first see:
PVRsupportDouble structure 
    double value 10
    structure control
        double outputValue 0.5
    alarm_t alarm MAJOR RECORD major high alarm 
        int severity 2
        int status 3
        string message major high alarm
    time_t timeStamp 2019-07-01 09:32:51.793  
        long secondsPastEpoch 1561987971
        int nanoseconds 792512003

value only went to 10 because that is the control limit. alarm shows the values determined by scalarAlarm . outputValue is .5 because of minStep.

Next you will see output like:

PVRsupportDouble structure 
    structure control
        double outputValue 1
    time_t timeStamp 2019-07-01 09:32:51.968  
        int nanoseconds 968025553

This will continue until You see:

PVRsupportDouble structure 
    structure control
        double outputValue 10
    time_t timeStamp 2019-07-01 09:33:00.974  
        int nanoseconds 973958435

No more output occurs because outputValue is now equal to value.

Later in the tutorial there is a similar example except for channel PVRsupportUByte.

scripts

The scripts all appear in:

mrk> pwd
/home/epicsv4/masterCPP/exampleCPP/support/client/scripts

The scripts with names that start with config or clear can be run either interactively or via arguments. The only exception is configAll

configControl

An example usage is:

mrk> ./configControl
recordName?
PVRsupportDouble
limitLow
-10
limitHigh
10
minStep
.5
Old : structure 
    control_t control
        double limitLow -10
        double limitHigh 10
        double minStep 0.5
        double outputValue 2.5
New : structure 
    control_t control
        double limitLow -10
        double limitHigh 10
        double minStep 0.5
        double outputValue 2.5
mrk> 

It could do the same without prompts via:

mrk> ./configControl PVRsupportDouble -10 10 .5
Old : structure 
    control_t control
        double limitLow -10
        double limitHigh 10
        double minStep 0.5
        double outputValue 2
New : structure 
    control_t control
        double limitLow -10
        double limitHigh 10
        double minStep 0.5
        double outputValue 2
mrk> 

The same could be done using pvput as follows:

mrk> pvput -r "control" PVRsupportDouble control='{"limitLow":"-10","limitHigh":"10","minStep":".5"}'

clearControl

Sets all control fields so that no control limits are present.

configScalarAlarm

An example usage is:

mrk> ./configScalarAlarm
recordName?
PVRsupportDouble
lowAlarmLimit
-8
lowWarningLimit
-6
highWarningLimit
6
highAlarmLimit
8
hysteresis
.1
Old : structure 
    scalarAlarm_t scalarAlarm
        double lowAlarmLimit -8
        double lowWarningLimit -6
        double highWarningLimit 6
        double highAlarmLimit 8
        double hysteresis 0.1
New : structure 
    scalarAlarm_t scalarAlarm
        double lowAlarmLimit -8
        double lowWarningLimit -6
        double highWarningLimit 6
        double highAlarmLimit 8
        double hysteresis 0.1
mrk> 

Just like configControl it can be invoked by giving all values as arguments.

clearScalarAlarm

Clears all the scalarAlarm fields.

configProcessRecord

example usage is:

mrk> ./configProcessRecord
processRecordName
PVRprocessRecord
command?
remove
recordName?
PVRscalarDouble
// result is
PVRprocessRecord
argument={"command":"remove","recordName":"PVRsupportDouble"}
Old : structure 
    structure argument
        string command add
        string recordName PVRscalarDouble
    structure result
        string status success
New : structure 
    structure argument
        string command remove
        string recordName PVRscalarDouble
    structure result
        string status success
mrk> 

It can also be invoked with arguments.

The following also works:

mrk> pvput -r "argument,result" PVRprocessRecord argument='{"command":"add","recordName":"PVRscalarDouble"}'
Old : structure 
    structure argument
        string command add
        string recordName PVRscalarDouble
    structure result
        string status PVRscalarDouble already present
New : structure 
    structure argument
        string command add
        spwd
tring recordName PVRscalarDouble
    structure result
        string status PVRscalarDouble already present

controlSigned

Convenience script for signed control limits.

#!/bin/sh
./configControl PVRsupportDouble -10 10 .5

controlUnsigned

Convenience script for unsigned control limits.

#!/bin/sh
./configControl PVRsupportUByte 1 20 1

scalarAlarmSigned

Convenience script for signed scalarAlarm limits.

#!/bin/sh
./configScalarAlarm PVRsupportDouble -8 -6 6 8 .1

scalarAlarmUnsigned

Convenience script for unsigned scalarAlarm limits.

#!/bin/sh
./configScalarAlarm PVRsupportUByte 2 4 16 18 1

configAll

Use of this was shown above.

#!/bin/bash
./controlSigned
./scalarAlarmSigned
./controlUnsigned
./scalarAlarmUnsigned
./configProcessRecord PVRprocessRecord add PVRsupportDouble
./configProcessRecord PVRprocessRecord add PVRsupportUByte
./configProcessRecord PVRprocessRecord add PVRscalarDouble
./configProcessRecord PVRprocessRecord add PVRscalarUByte

Client Examples

Record Support Examples

Records PVRsupportDouble and PVRsupportUByte both use the control and scalarAlarm record support from pvDatabaseCPP.

Previously there was an example accessing PVRsupportDouble. Now lets look at an example accessing PVRsupportUByte.

After starting the database and running configAll, look at:

mrk> pvget -r "control,scalarAlarm" PVRsupportUByte
PVRsupportUByte structure 
    control_t control
        double limitLow 1
        double limitHigh 20
        double minStep 1
        ubyte outputValue 0
    scalarAlarm_t scalarAlarm
        double lowAlarmLimit 2
        double lowWarningLimit 4
        double highWarningLimit 16
        double highAlarmLimit 18
        double hysteresis 1
Look at the control and scalarAlarm settings.

In one window:

mrk> pvget -r "value,alarm,timeStamp,control.outputValue" -v -m PVRsupportUByte

Keep this window and see what happens when you run the following in another window:

mrk> pvput PVRsupportUByte 40

In the first window you will see that value goes to 20 and the alarm changes. It did NOT go to 40 because control.limitHigh is 20. But control.outputValue changes by 1 each time the record processes. On each process except the first only timeStamp and control.outputValue change value. This continues until control.outputValue is 20. Then no more monitors occur.

Now issue some more pvputs. For example to 19 then 15 then 5 then 0.

Plugin Examples

array

The records PVRscalarArrayDouble, PVRscalarArrayUByte, PVRscalarArrayString, and PVRscalarArrayBoolean are created in order to see the array plugin in action.

mrk> pwd
/home/epicsv4/masterCPP/exampleCPP/support/client/scripts
mrk> cat exampleArrayPlugin
#!/bin/sh
echo "unsigned byte array"
pvput PVRscalarArrayUByte value=[1,2,3,4,5,6,7,8,9,10]
pvput -r "value[array=1:3]" PVRscalarArrayUByte value=[10,20,30]
pvget -r "value[array=1:3]" PVRscalarArrayUByte
pvget PVRscalarArrayUByte
pvput PVRscalarArrayUByte value=[1,2,3,4,5,6,7,8,9,10]
pvput -r "value[array=1:2:5]" PVRscalarArrayUByte value=[10,20,30]
pvget -r "value[array=1:2:5]" PVRscalarArrayUByte
pvget PVRscalarArrayUByte
echo "double array"
pvput PVRscalarArrayDouble value='[".1",".2",".3",".4",".5",".6",".7",".8",".9","1.0"]'
pvput -r "value[array=1:3]" PVRscalarArrayDouble value=[10,20,30]
pvget -r "value[array=1:3]" PVRscalarArrayDouble
pvget PVRscalarArrayDouble
pvput PVRscalarArrayDouble value='[".1",".2",".3",".4",".5",".6",".7",".8",".9","1.0"]'
pvput -r "value[array=1:2:5]" PVRscalarArrayDouble value=[10,20,30]
pvget -r "value[array=1:2:5]" PVRscalarArrayDouble
pvget PVRscalarArrayDouble
echo "string array"
pvput PVRscalarArrayString value='["zero","one","two","three","four","five","six","seven","eight","nine"]'
pvput -r "value[array=1:3]" PVRscalarArrayString value='["now is zero","now is one","now is two"]'
pvget -r "value[array=1:3]" PVRscalarArrayString
pvget PVRscalarArrayString
pvput PVRscalarArrayString value='["zero","one","two","three","four","five","six","seven","eight","nine"]'
pvput -r "value[array=1:2:5]" PVRscalarArrayString value='["now is zero","now is one","now is two"]'
pvget -r "value[array=1:2:5]" PVRscalarArrayString
pvget PVRscalarArrayString
echo "boolean array"
pvput PVRscalarArrayBoolean value='["true","true","true","true","true","true","true","true","true","true"]'
pvput -r "value[array=1:3]" PVRscalarArrayBoolean value='["false","false","false"]'
pvget -r "value[array=1:3]" PVRscalarArrayBoolean
pvget PVRscalarArrayBoolean
pvput PVRscalarArrayBoolean value='["true","true","true","true","true","true","true","true","true","true"]'
pvput -r "value[array=1:2:5]" PVRscalarArrayBoolean value='["false","false","false"]'
pvget -r "value[array=1:2:5]" PVRscalarArrayBoolean
pvget PVRscalarArrayBoolean

Just issue the command:

mrk> ./exampleArrayPlugin

The following are some additional examples just using pvput and pvget:

mrk> pvput PVRscalarArrayUByte value='[1,2,3,4,5,6,7,8,9,10]'
Old : ...
New : 2019-07-01 10:08:49.163  [1,2,3,4,5,6,7,8,9,10]
mrk> pvget -r "value[array=2:4]" PVRscalarArrayUByte
PVRscalarArrayUByte [3,4,5]
mrk> pvput -r "value[array=2:4]" PVRscalarArrayUByte value='[10,20,30]'
Old : [3,4,5]
New : [10,20,30]
mrk> pvget -r "value[array=2:4]" PVRscalarArrayUByte
PVRscalarArrayUByte [10,20,30]
mrk> pvget  PVRscalarArrayUByte
PVRscalarArrayUByte 2019-07-01 10:10:49.341  [1,2,10,20,30,6,7,8,9,10]

NOTE: The syntax for setting value, i. e.

value='[10,20,30]'
Also could be:
value='["10","20","30"]'
In fact if the element values are anything except an integer, each element must be enclosed in quotes.

deadband and ignore

The records PVRscalarDouble and PVRscalarUByte are both created to show usage of the deadband and ignore plugins. Each record generates a sawtooth. Each time it processes it makes a step.

There is an example script:

mrk> pwd
/home/epicsv4/masterCPP/exampleCPP/support/client/scripts
mrk> ./exampleDeadbandPlugin
recordName?
PVRscalarDouble
deadband?
abs:1
PVRscalarDouble structure 
    time_t timeStamp 2019-06-20 14:23:34.508  
        long secondsPastEpoch 1561055014
        int nanoseconds 508198078
        int userTag 0
    double value -3
....
^Cmrk> 

The same thing can be done via:

mrk> pvget -m -v -r "timeStamp[ignore=true],alarm[ignore=true],value[deadband=abs:1]" PVRscalarDouble 

Now try different values for deadband.

Building support

This section describes the files that appear in exampleCPP/support. This example is a good way to help understand how to build a service based on PVRecords. The PVRecords can be in a standalone pvDatabase or can be in an IOC that also contains DBRecords.

After executing make, exampleCPP/support has the following subdirectories:

mrk> ls | cat
bin           // generated by make
configure     // standard files for building a V4/V3 appplication
db            // generated by make
dbd           // generated by make
include       // generated by make
ioc           // see below
iocBoot       // see below
lib           // generated by make
Makefile      // see below
README.md
src           // see below
client        // was described in previous section

support/src

This has the files:

pv/
    supportRecord.h
    scalarArrayRecord.h
    scalarRecord.h
supportRecord.cpp
supportRecordRegister.dbd
supportRecordRegister.cpp
supportMain.cpp
scalarArrayRecord.cpp
scalarArrayRecordRegister.dbd
scalarArrayRecordRegister.cpp
scalarRecord.cpp
scalarRecordRegister.dbd
scalarRecordRegister.cpp
Makefile

support/src - supportRecord

This section describes the code that implements the supportRecord.

pv/supportRecord.h

This describes the supportRecord implemented for exampleCPP/support. Look at it while also looking at supportRecord.cpp. Note the public methods are create, init, and process.

Note also that it has the following include statements:

#include <shareLib.h>

This statement is for building DLL shared libraries on windows. All other includes must appear before this include. Next it is explained what supportRecord.cpp must do. Any code that is creating an object module library should follow these rules.

supportRecord.cpp

includes

Before defining any methods the following appears:

...
#include <pv/channelProviderLocal.h>

#define epicsExportSharedSymbols
#include "pv/supportRecord.h"

The above does the following:

You can check that you have defined all required includes by look at

mrk> cat O.linux-x86_64/scalarRecord.d
...   // LOTS OF OUTPUT
 ../pv/scalarRecord.h  // THIS SHOULD APPEAR LAST
mrk> 
create

The create method is:

SupportRecordPtr SupportRecord::create(
    std::string const & recordName,epics::pvData::ScalarType scalarType)
{
    FieldCreatePtr fieldCreate = getFieldCreate();
    PVDataCreatePtr pvDataCreate = getPVDataCreate();
    StandardFieldPtr standardField = getStandardField();
    StructureConstPtr  topStructure = fieldCreate->createFieldBuilder()->
        add("value",scalarType) ->
        add("reset",pvBoolean) ->
        add("alarm",standardField->alarm()) ->
        add("timeStamp",standardField->timeStamp()) ->
        add("display",standardField->display()) ->
        add("control",ControlSupport::controlField(scalarType)) ->
        add("scalarAlarm",ScalarAlarmSupport::scalarAlarmField()) ->
        createStructure();
    PVStructurePtr pvStructure = pvDataCreate->createPVStructure(topStructure);
    SupportRecordPtr pvRecord(
        new SupportRecord(recordName,pvStructure));
    if(!pvRecord->init()) pvRecord.reset();
    return pvRecord;
}

The above does the following:

init

The init method is:

bool SupportRecord::init()
{
    initPVRecord();
    PVRecordPtr pvRecord = shared_from_this();
    PVStructurePtr pvStructure(getPVStructure());
    controlSupport = ControlSupport::create(pvRecord);
    bool result = controlSupport->init(
       pvStructure->getSubField("value"),pvStructure->getSubField("control"));
    if(!result) return false;
    scalarAlarmSupport = ScalarAlarmSupport::create(pvRecord);
    result = scalarAlarmSupport->init(
       pvStructure->getSubField("value"),
       pvStructure->getSubField<PVStructure>("alarm"),
       pvStructure->getSubField("scalarAlarm"));
    if(!result) return false;
    pvReset = getPVStructure()->getSubField<PVBoolean>("reset");
    return true;
}
where
initPVRecord
This method is implemented by the PVRecord base class. It must be called by any derived class that implements method init.
controlSupport
These statement create the control support, which is implemented by pvDatabaseCPP.
scalarAlarmSupport
These statement create the scalarAlarm support, which is implemented by pvDatabaseCPP.
process

The process method is:

void SupportRecord::process()
{
    bool wasChanged = false;
    if(pvReset->get()==true) {
        pvReset->put(false);
        controlSupport->reset();
        scalarAlarmSupport->reset();
    } else {
        if(controlSupport->process()) wasChanged = true;;
        if(scalarAlarmSupport->process()) wasChanged = true;
    }
    if(wasChanged) PVRecord::process();
}
where
wasChanged
This is initialized to false. It stays false unless a support returns true, which means that the support modified some field. If it does return true then the base class process method is called, which sets the timeStamp field to the current time.
pvReset
If this is true then the reset method of each support is called.
controlSupport
The control support is called.
scalarAlarmSupport
The scalarAlarm support is called. Note that it is called after control support, because control support might change the value field.

supportRecordRegister.dbd

This has the single statement:

registrar("supportRecordRegister")
This is used along with the following source file to allow the st.cmd file to create SupportRecord instances.

supportRecordRegister.cpp

NOTE: This must follow the same include file rules as supportRecord.cpp.

This creates an iocshell command to create SupportRecord instances. It is what is called for the following lines in the st.cmd file.

supportRecordCreate PVRsupportDouble
supportRecordCreate PVRsupportUByte pvUByte

It implements code defined by epics-base to implement an iocshell command:

static const iocshArg testArg0 = { "recordName", iocshArgString };
static const iocshArg testArg1 = { "scalarType", iocshArgString };
static const iocshArg *testArgs[] = {
    &testArg0,&testArg1};

static const iocshFuncDef supportRecordFuncDef = {"supportRecordCreate", 2,testArgs};

static void supportRecordCallFunc(const iocshArgBuf *args)
{
    char *recordName = args[0].sval;
    if(!recordName) {
        throw std::runtime_error("supportRecordCreate invalid number of arguments");
    }
    char *stype = args[1].sval;
    epics::pvData::ScalarType scalarType  = epics::pvData::pvDouble;
    if(stype) {
        string val(stype);
        if(val.compare("pvByte")==0) {
            scalarType = epics::pvData::pvByte;
        } else if(val.compare("pvShort")==0) {
            scalarType = epics::pvData::pvShort;
        } else if(val.compare("pvInt")==0) {
            scalarType = epics::pvData::pvInt;
        } else if(val.compare("pvLong")==0) {
            scalarType = epics::pvData::pvLong;
        } else if(val.compare("pvFloat")==0) {
            scalarType = epics::pvData::pvFloat;
        } else if(val.compare("pvDouble")==0) {
            scalarType = epics::pvData::pvDouble;
        } else if(val.compare("pvUByte")==0) {
            scalarType = epics::pvData::pvUByte;
        } else if(val.compare("pvUShort")==0) {
            scalarType = epics::pvData::pvUShort;
        } else if(val.compare("pvUInt")==0) {
            scalarType = epics::pvData::pvUInt;
        } else if(val.compare("pvULong")==0) {
            scalarType = epics::pvData::pvULong;
        } else {
             cout << val << " is not a support scalar type\n";
             return; 
        }
    }
    SupportRecordPtr record = SupportRecord::create(recordName,scalarType);
    bool result = PVDatabase::getMaster()->addRecord(record);
    if(!result) cout << "recordname" << " not added" << endl;
}

static void supportRecordRegister(void)
{
    static int firstTime = 1;
    if (firstTime) {
        firstTime = 0;
        iocshRegister(&supportRecordFuncDef, supportRecordCallFunc);
    }
}

extern "C" {
    epicsExportRegistrar(supportRecordRegister);
}
where
define arguments
Defines arguments for recordName and scalarType
epics::pvData::ScalarType scalarType
Initializes this to be pvDouble and then if the command line includes a second argument sets it to what is requested.
supportRecordRegister
Register the command with epics-base.
epicsExportRegistrar
when ioc is build this statement is executed as a result of supportRegisted.dbd

supportMain.cpp

This creates a main program that implements the example directly instead of being part of a V3 IOC.

int main(int argc,char *argv[])
{
    PVDatabasePtr master = PVDatabase::getMaster();
    ChannelProviderLocalPtr channelProvider = getChannelProviderLocal();
    PVRecordPtr pvRecord;
    string recordName;

    recordName = "PVRtraceRecord";
    pvRecord = TraceRecord::create(recordName);
    master->addRecord(pvRecord);
    recordName = "PVRremoveRecord";
    pvRecord = RemoveRecord::create(recordName);
    master->addRecord(pvRecord);
    recordName = "PVRprocessRecord";
    pvRecord = ProcessRecord::create(recordName,.5);
    master->addRecord(pvRecord);
    recordName = "PVRsupportDouble";
    pvRecord = SupportRecord::create(recordName,epics::pvData::pvDouble);
    master->addRecord(pvRecord);
    recordName = "PVRsupportUByte";
    pvRecord = SupportRecord::create(recordName,epics::pvData::pvUByte);
    master->addRecord(pvRecord);
    recordName = "PVRscalarArrayDouble";
    pvRecord = ScalarArrayRecord::create(recordName,epics::pvData::pvDouble);
    master->addRecord(pvRecord);
    recordName = "PVRscalarArrayUByte";
    pvRecord = ScalarArrayRecord::create(recordName,epics::pvData::pvUByte);
    master->addRecord(pvRecord);
    recordName = "PVRscalarArrayString";
    pvRecord = ScalarArrayRecord::create(recordName,epics::pvData::pvString);
    master->addRecord(pvRecord);
    recordName = "PVRscalarArrayBoolean";
    pvRecord = ScalarArrayRecord::create(recordName,epics::pvData::pvBoolean);
    master->addRecord(pvRecord);
    recordName = "PVRscalarDouble";
    pvRecord = ScalarRecord::create(recordName,epics::pvData::pvDouble,-10,10,.5);
    master->addRecord(pvRecord);
    recordName = "PVRscalarUByte";
    pvRecord = ScalarRecord::create(recordName,epics::pvData::pvUByte,0,20,1);
    master->addRecord(pvRecord);


    ServerContext::shared_pointer ctx =
        startPVAServer("local",0,true,true);
    string str;
    while(true) {
        cout << "Type exit to stop: \n";
        getline(cin,str);
        if(str.compare("exit")==0) break;

    }
    return 0;
}

support/src - scalarArrayRecord

This section describes the code that implements the scalarArrayRecord.

pv/scalarArrayRecord.h

This describes the scalarArrayRecord implemented for exampleCPP/support. Look at it while also looking at scalarArrayRecord.cpp. Note the only public method is create.

This is a way to create a "soft" record, i.e the code only creates a pvStructure for a pvRecord and lets the base class for PVRecord do everthing else.

pv/scalarArrayRecord.cpp

The create method is:

ScalarArrayRecordPtr ScalarArrayRecord::create(
    string const & recordName,
    epics::pvData::ScalarType scalarType)
{
    FieldCreatePtr fieldCreate = getFieldCreate();
    PVDataCreatePtr pvDataCreate = getPVDataCreate();
    StandardFieldPtr standardField = getStandardField();
    StructureConstPtr  topStructure = fieldCreate->createFieldBuilder()->
        addArray("value",scalarType) ->
        add("timeStamp",standardField->timeStamp()) ->
        createStructure();
    PVStructurePtr pvStructure = pvDataCreate->createPVStructure(topStructure);
    ScalarArrayRecordPtr pvRecord(
        new ScalarArrayRecord(recordName,pvStructure));
    pvRecord->init();
    return pvRecord;
}

The above creates a pvRecord that only has a scalar value field and a timeStamp.

scalarArrayRecordRegister.dbd

This has the single statement:

registrar("scalarArrayRecordRegister")

scalarArrayRecordRegister.cpp

This is similar to supportRecordRegister.cpp.

support/src - scalarRecord

This section describes the code that implements the scalarRecord. This record generates a saw-tooth output. The st.cmd file has the statements:

scalarRecordCreate PVRscalarDouble pvDouble -10 10 .5
scalarRecordCreate PVRscalarUByte pvUByte 0 20 1
The arguments are:
recordName
The pvRecord name.
scalarType
The scalar type, which must be a numeric type.
minValue
Minimum value
maxValue
Maximum value
stepSize
The amount to change each time process is called.

Look at the process method generates a sawtooth with values between minValue and maxValue. Each time process is called it changes the value by stepSize.

pv/scalarRecord.h

This describes the supportRecord implemented for exampleCPP/support. Look at it while also looking at supportRecord.cpp. Note the public methods are create, init, and process.

scalarRecord.cpp

The create method is:

ScalarRecordPtr ScalarRecord::create(
    string const & recordName,
    epics::pvData::ScalarType scalarType,
    double minValue,
    double maxValue,
    double stepSize)
{
    FieldCreatePtr fieldCreate = getFieldCreate();
    PVDataCreatePtr pvDataCreate = getPVDataCreate();
    StandardFieldPtr standardField = getStandardField();
    StructureConstPtr  topStructure = fieldCreate->createFieldBuilder()->
        add("value",scalarType) ->
        add("timeStamp",standardField->timeStamp()) ->
        createStructure();
    PVStructurePtr pvStructure = pvDataCreate->createPVStructure(topStructure);
    ScalarRecordPtr pvRecord(
        new ScalarRecord(recordName,pvStructure,minValue,maxValue,stepSize));
    if(!pvRecord->init()) pvRecord.reset();
    return pvRecord;
}

The init method is:

bool ScalarRecord::init()
{
    initPVRecord();
    pvValue = getPVStructure()->getSubField<PVScalar>("value");
    if(!pvValue) return false;
    return true;
}

The process method is:

void ScalarRecord::process()
{
    ConvertPtr convert = getConvert();
    double value = convert->toDouble(pvValue);
    if(stepPositive) {
        value = value + stepSize;
        if(value>maxValue) {
           stepPositive = false;
           value = maxValue;
        }
    } else {
        value = value - stepSize;
        if(value<minValue) {
             stepPositive = true;
             value = minValue;
        }
    }
    convert->fromDouble(pvValue,value);
    PVRecord::process();
}

scalarRecordRegister.dbd

This has the single statement:

registrar("scalarRecordRegister")

This is used along with the following source file to allow the st.cmd file to create scalarRecord instances.

scalarRecordRegister.cpp

static const iocshArg testArg0 = { "recordName", iocshArgString };
static const iocshArg testArg1 = { "scalarType", iocshArgString };
static const iocshArg testArg2 = { "minValue", iocshArgDouble };
static const iocshArg testArg3 = { "maxValue", iocshArgDouble };
static const iocshArg testArg4 = { "stepSize", iocshArgDouble };
static const iocshArg *testArgs[] = {
    &testArg0,&testArg1,&testArg2,&testArg3,&testArg4};

static const iocshFuncDef scalarRecordFuncDef = {"scalarRecordCreate", 5,testArgs};

static void scalarRecordCallFunc(const iocshArgBuf *args)
{
    char *recordName = args[0].sval;
    if(!recordName) {
        throw std::runtime_error("scalarRecordCreate invalid number of arguments");
    }
    char *stype = args[1].sval;
    epics::pvData::ScalarType scalarType  = epics::pvData::pvDouble;
    if(stype) {
        string val(stype);
        if(val.compare("pvByte")==0) {
            scalarType = epics::pvData::pvByte;
        } else if(val.compare("pvShort")==0) {
            scalarType = epics::pvData::pvShort;
        } else if(val.compare("pvInt")==0) {
            scalarType = epics::pvData::pvInt;
        } else if(val.compare("pvLong")==0) {
            scalarType = epics::pvData::pvLong;
        } else if(val.compare("pvFloat")==0) {
            scalarType = epics::pvData::pvFloat;
        } else if(val.compare("pvDouble")==0) {
            scalarType = epics::pvData::pvDouble;
        } else if(val.compare("pvUByte")==0) {
            scalarType = epics::pvData::pvUByte;
        } else if(val.compare("pvUShort")==0) {
            scalarType = epics::pvData::pvUShort;
        } else if(val.compare("pvUInt")==0) {
            scalarType = epics::pvData::pvUInt;
        } else if(val.compare("pvULong")==0) {
            scalarType = epics::pvData::pvULong;
        } else if(val.compare("pvString")==0) {
            scalarType = epics::pvData::pvString;
        } else if(val.compare("pvBoolean")==0) {
            scalarType = epics::pvData::pvBoolean;
        } else {
             cout << val << " is not a scalar type\n";
             return; 
        }
    }
    double minValue = args[2].dval;
    double maxValue = args[3].dval;
    double stepSize = args[4].dval;
    ScalarRecordPtr record = ScalarRecord::create(
        recordName,scalarType,minValue,maxValue,stepSize);
    bool result = PVDatabase::getMaster()->addRecord(record);
    if(!result) cout << "recordname" << " not added" << endl;
}

static void scalarRecordRegister(void)
{
    static int firstTime = 1;
    if (firstTime) {
        firstTime = 0;
        iocshRegister(&scalarRecordFuncDef, scalarRecordCallFunc);
    }
}

extern "C" {
    epicsExportRegistrar(scalarRecordRegister);
}

Makefile

The Makefile is:

TOP=..
include $(TOP)/configure/CONFIG
supportSRC = $(TOP)/src

EPICS_BASE_PVA_CORE_LIBS = pvDatabase pvAccess pvAccessCA pvData ca Com
INC += pv/supportRecord.h
INC += pv/scalarArrayRecord.h
INC += pv/scalarRecord.h

DBD += supportRecordRegister.dbd
DBD += scalarArrayRecordRegister.dbd
DBD += scalarRecordRegister.dbd

LIBRARY = support
LIBSRCS += supportRecord.cpp
LIBSRCS += supportRecordRegister.cpp
LIBSRCS += scalarArrayRecord.cpp
LIBSRCS += scalarArrayRecordRegister.cpp
LIBSRCS += scalarRecord.cpp
LIBSRCS += scalarRecordRegister.cpp
support_LIBS += $(EPICS_BASE_PVA_CORE_LIBS)

# shared library ABI version.
SHRLIB_VERSION ?= 4.3.0

PROD_HOST += supportMain
supportMain_SRCS += supportMain.cpp
supportMain_LIBS += support
supportMain_LIBS += $(EPICS_BASE_PVA_CORE_LIBS)

PROD_SYS_LIBS_WIN32 += ws2_32


include $(TOP)/configure/RULES

where
EPICS_BASE_PVA_CORE_LIBS
This defines all the object libraries required from other sources.
INC
These define the *.h files that should be installed into include/pv.
DBD
These define the *.dbd files that should be installed into dbd.
LIBRARY
This defines the object library to create.
LIBSRCS
These define the source modules for the LIBRARY.
PROD_HOST
This creates supportMain.

support/ioc

This directory creates the IOC code for this example.

Db

This creates a template for creating an ao DBRecord. Note that the control support from pvDatabase is modeled after the ao record.

src

The is where the code for the IOC is built. In particular look at supportInclude.dbd:

include "base.dbd"                          // standard for V4 
include "PVAClientRegister.dbd"             // standard for V4              
include "PVAServerRegister.dbd"             // standard for V4 
include "registerChannelProviderLocal.dbd"  // standard for V4 
include "qsrv.dbd"                          // standard for V4 
include "traceRecordRegister.dbd"           // from pvDatabaseCPP
include "removeRecordRegister.dbd"          // from pvDatabaseCPP
include "processRecordRegister.dbd"         // from pvDatabaseCPP
include "supportRecordRegister.dbd"         // from THIS example
include "scalarArrayRecordRegister.dbd"     // from THIS example
include "scalarRecordRegister.dbd"          // from THIS example

support/iocBoot

iocBoot/support

st.cmd is:

< envPaths

cd ${TOP}

## Register all support components
dbLoadDatabase("dbd/support.dbd")
support_registerRecordDeviceDriver(pdbbase)

## Load record instances
dbLoadRecords("db/ao.db","name=DBRsupportAo");

cd ${TOP}/iocBoot/${IOC}
iocInit()
traceRecordCreate PVRtraceRecord
removeRecordCreate PVRremoveRecord
processRecordCreate PVRprocessRecord .5
supportRecordCreate PVRsupportDouble
supportRecordCreate PVRsupportUByte pvUByte
scalarArrayRecordCreate PVRscalarArrayDouble pvDouble
scalarArrayRecordCreate PVRscalarArrayUByte pvUByte
scalarArrayRecordCreate PVRscalarArrayString pvString
scalarArrayRecordCreate PVRscalarArrayBoolean pvBoolean
scalarRecordCreate PVRscalarDouble pvDouble -10 10 .5
scalarRecordCreate PVRscalarUByte pvUByte 0 20 1