This product is available via an open source license
The following describes how services can be implemented via pvaClient and pvDatabase. The basic idea is that a number of support modules, each implemented via a PVRecord, can be created. The process method implements the support details.
A service can be created that uses the support modules. The service can implement a PVDatabase or can be part of an IOC.
In order to understand the following exampleCPP should be cloned. It has all the examples described below.
The next section shows how to build exampleCPP from an EPICS7 release.
After you have build exampleCPP then start one of the following:
pwd SOMEWHERE/exampleCPP/database/iocBoot/exampleDatabase ../../bin/linux-x86_64/exampleDatabase st.cmdor
pwd SOMEWHERE/exampleCPP/serviceMain bin/linux-x86_64/serviceMainNOTE: do not start both because they have many of the same channels.
Section 3 describes serviceMain, which implements a service as a main program.
Following serviceMain is a description of database, which implements a service as part of an IOC database.
The remaining sections describes the support modules used by both serviceMain and database.
The following examples are using linux. I have built release 7.0.5 at
pwd /home/install/epics/base-7.0.5 cd modules
exampleCPP requires changes to pvaClient and pvDatabase that have been made since 7.0.5. But each can be replaced by the latest via the following:
/bin/rm -fr pvaClient pvDatabase git clone https://github.com/epics-base/pvaClientCPP pvaClient git clone https://github.com/epics-base/pvDatabaseCPP pvDatabase cd pvaClient make cd .. cd pvDatabase make cd ..
git clone https://github.com/epics-base/exampleCPP cd exampleCPP cp MasterRELEASE.local RELEASE.local
You have to edit RELEASE.local.
The only line You need to change is the one starting with EPICS7_DIR=
This must show where you are building the EPICS7 release.
Then enter:
cp RELEASE.local configure make
SCALARLIMITRECORD=$(TOP)/../scalarLimit POWERSUPPLYRECORD=$(TOP)/../powerSupplyRecord LINKRECORD=$(TOP)/../linkRecord HELLOPUTGETRECORD=$(TOP)/../helloPutGetRecord HELLORPCRECORD=$(TOP)/../helloRPCRecord CONTROLRECORD=$(TOP)/../controlRecordThis refers to each of the support modules being used.
grep RELEASE.support configure/RELEASE include $(TOP)/RELEASE.support
TOP=.. include $(TOP)/configure/CONFIG # NOTE: The order of the following is important EPICS_BASE_PVA_CORE_LIBS = pvaClient pvDatabase pvAccess pvAccessCA pvData ca Com PROD_HOST += serviceMain # NOTE: The order of the following is important serviceMain_SRCS += serviceMain.cpp serviceMain_LIBS += scalarLimitRecord serviceMain_LIBS += powerSupplyRecord serviceMain_LIBS += linkRecord serviceMain_LIBS += helloPutGetRecord serviceMain_LIBS += helloRPCRecord serviceMain_LIBS += controlRecord serviceMain_LIBS += $(EPICS_BASE_PVA_CORE_LIBS) include $(TOP)/configure/RULES
The following are just a few important details. Look at the source for more.
Note the following includes for the support modules:
#include <pv/pvdbcrScalarRecord.h> #include <pv/pvdbcrScalarArrayRecord.h> #include <pv/pvdbcrAddRecord.h> #include <pv/pvdbcrRemoveRecord.h> #include <pv/pvdbcrProcessRecord.h> #include <pv/pvdbcrTraceRecord.h> #include <scalarLimit/scalarLimitRecord.h> #include <powerSupply/powerSupplyRecord.h> #include <linkRecord/getLinkScalarRecord.h> #include <linkRecord/getLinkScalarArrayRecord.h> #include <linkRecord/putLinkScalarRecord.h> #include <linkRecord/putLinkScalarArrayRecord.h> #include <helloPutGet/helloPutGetRecord.h> #include <helloRPC/helloRPCRecord.h> #include <pvcontrol/controlRecord.h>
These are for all the supports used by serviceMain. The order is important.
The main program creates all it's PVRecord via the support. Excerpts from the main program are:
int main(int argc,char *argv[])
{
PVDatabasePtr master = PVDatabase::getMaster();
ChannelProviderLocalPtr channelProvider = getChannelProviderLocal();
std::vector<std::string> recordNames;
std::vector<std::string> valueType;
recordNames.push_back("PVRboolean"); valueType.push_back("boolean");
recordNames.push_back("PVRbyte"); valueType.push_back("byte");
...
recordNames.push_back("PVRdouble"); valueType.push_back("double");
recordNames.push_back("PVRstring"); valueType.push_back("string");
for(size_t i=0;i<recordNames.size(); ++i)
{
if(!master->addRecord(PvdbcrScalarRecord::create(recordNames[i],valueType[i]))) {
cerr << "record " << recordNames[i] << " not added to master\n";
}
}
recordNames.clear();
valueType.clear();
recordNames.push_back("PVRbooleanArray"); valueType.push_back("boolean");
recordNames.push_back("PVRbyteArray"); valueType.push_back("byte");
...
recordNames.push_back("PVRdoubleArray"); valueType.push_back("double");
recordNames.push_back("PVRstringArray"); valueType.push_back("string");
for(size_t i=0;i<recordNames.size(); ++i)
{
if(!master->addRecord(PvdbcrScalarArrayRecord::create(recordNames[i],valueType[i]))) {
cerr << "record " << recordNames[i] << " not added to master\n";
}
}
std::string recordName;
recordName = "PVRaddRecord";
if(!master->addRecord(PvdbcrAddRecord::create(recordName))) {
cerr << "record " << recordName << " not added to master\n";
}
recordName = "PVRremoveRecord";
if(!master->addRecord(PvdbcrRemoveRecord::create(recordName))) {
cerr << "record " << recordName << " not added to master\n";
}
recordName = "PVRprocessRecord";
if(!master->addRecord(PvdbcrProcessRecord::create(recordName))) {
cerr << "record " << recordName << " not added to master\n";
}
recordName = "PVRtraceRecord";
if(!master->addRecord(PvdbcrTraceRecord::create(recordName))) {
cerr << "record " << recordName << " not added to master\n";
}
recordName = "PVRcontrolDouble";
epics::example::control::ControlRecordPtr controlRecordDouble
= epics::example::control::ControlRecord::create(recordName,"double");
if(!master->addRecord(controlRecordDouble)) {
cerr << "record " << recordName << " not added to master\n";
}
recordName = "PVRcontrolUbyte";
epics::example::control::ControlRecordPtr controlRecordUbyte
= epics::example::control::ControlRecord::create(recordName,"ubyte");
master->addRecord(controlRecordUbyte);
recordName = "PVRpowerSupply";
epics::example::powerSupply::PowerSupplyRecordPtr powerSupply
= epics::example::powerSupply::PowerSupplyRecord::create(recordName);
if(!master->addRecord(powerSupply)) {
cerr << "record " << recordName << " not added to master\n";
}
recordName = "PVRgetLinkScalar";
epics::example::linkRecord::GetLinkScalarRecordPtr getLinkScalar
= epics::example::linkRecord::GetLinkScalarRecord::create(recordName);
if(!master->addRecord(getLinkScalar)) {
cerr << "record " << recordName << " not added to master\n";
}
recordName = "PVRgetLinkScalarArray";
epics::example::linkRecord::GetLinkScalarArrayRecordPtr getLinkScalarArray
= epics::example::linkRecord::GetLinkScalarArrayRecord::create(recordName);
if(!master->addRecord(getLinkScalarArray)) {
cerr << "record " << recordName << " not added to master\n";
}
recordName = "PVRputLinkScalar";
epics::example::linkRecord::PutLinkScalarRecordPtr putLinkScalar
= epics::example::linkRecord::PutLinkScalarRecord::create(recordName);
if(!master->addRecord(putLinkScalar)) {
cerr << "record " << recordName << " not added to master\n";
}
recordName = "PVRputLinkScalarArray";
epics::example::linkRecord::PutLinkScalarArrayRecordPtr putLinkScalarArray
= epics::example::linkRecord::PutLinkScalarArrayRecord::create(recordName);
if(!master->addRecord(putLinkScalarArray)) {
cerr << "record " << recordName << " not added to master\n";
}
recordName = "PVRhelloPutGet";
epics::example::helloPutGet::HelloPutGetRecordPtr helloPutGet
= epics::example::helloPutGet::HelloPutGetRecord::create(recordName);
if(!master->addRecord(helloPutGet)) {
cerr << recordName << " not added to master\n";
}
recordName = "PVRhelloRPC";
epics::example::helloRPC::HelloRPCRecordPtr helloRPC
= epics::example::helloRPC::HelloRPCRecord::create(recordName);
if(!master->addRecord(helloRPC)) {
cerr << recordName << " not added to master\n";
}
recordName = "PVRscalarLimitUbyte";
epics::scalarLimit::ScalarLimitRecordCreate::create(recordName,"ubyte");
recordName = "PVRscalarLimitDouble";
epics::scalarLimit::ScalarLimitRecordCreate::create(recordName,"double");
ServerContext::shared_pointer ctx =
startPVAServer("local",0,true,true);
string str;
while(true) {
cout << "enter: pvdbl or exit \n";
getline(cin,str);
if(str.compare("exit")==0) break;
if(str.compare("pvdbl")==0) {
PVStringArrayPtr pvNames = master->getRecordNames();
PVStringArray::const_svector xxx = pvNames->view();
for(size_t i=0; i<xxx.size(); ++i) cout<< xxx[i] << endl;
}
}
return 0;
}
After starting enter pvdbl to see all that records.
This directory has the following files:
TOP=.. include $(TOP)/configure/CONFIG # NOTE: The order of the following is important EPICS_BASE_PVA_CORE_LIBS = pvaClient pvDatabase pvAccess pvAccessCA nt pvData ca Com INC += pv/exampleDatabase.h DBD += exampleDatabaseRegister.dbd LIBRARY = exampleDatabase LIBSRCS += exampleDatabase.cpp LIBSRCS += exampleDatabaseRegister.cpp exampleDatabase_LIBS += $(EPICS_BASE_PVA_CORE_LIBS) # needed for Windows LIB_SYS_LIBS_WIN32 += netapi32 ws2_32 include $(TOP)/configure/RULES
This has a number of private methods:
PVRstructureArray
structure
structure[] value
PVRrestrictedUnion
epics:nt/NTUnion:1.0
union value
(none)
time_t timeStamp
long secondsPastEpoch 0
int nanoseconds 0
int userTag 0
PVRvariantUnion
epics:nt/NTUnion:1.0
any value
(none)
time_t timeStamp
long secondsPastEpoch 0
int nanoseconds 0
int userTag 0
PVRrestrictedUnionArray
structure
time_t timeStamp
long secondsPastEpoch 0
int nanoseconds 0
int userTag 0
union[] value
PVRvariantUnionArray
structure
time_t timeStamp
long secondsPastEpoch 0
int nanoseconds 0
int userTag 0
any[] value
PVRBigRecord
structure
time_t timeStamp <undefined> 0
structure scalar
structure boolean
boolean value false
structure byte
byte value 0
structure long
long value 0
structure double
double value 0
structure string
string value
structure scalarArray
structure boolean
boolean[] value []
structure byte
byte[] value []
structure long
long[] value []
structure double
double[] value []
structure string
string[] value []
structure[] structureArray
union restrictedUnion
(none)
any variantUnion
(none)
This is a static method of ExampleDatabase that creates all the PVRecords that make up the example database.
It does the following:
This has V3 record templates for creating DBRecords. They are used when the example database is started as part of a V3 IOC.
This has:
TOP=../.. include $(TOP)/configure/CONFIG LIBRARY_IOC += example # create DBRecord types DBD += exampleDatabase.dbd DBDINC += simpleBusyRecord DBDINC += charoutRecord DBDINC += shortoutRecord DBDINC += ucharoutRecord DBDINC += ushortoutRecord DBDINC += ulongoutRecord DBDINC += floatoutRecord example_SRCS += simpleBusyRecord.c example_SRCS += charoutRecord.c example_SRCS += shortoutRecord.c example_SRCS += ucharoutRecord.c example_SRCS += ushortoutRecord.c example_SRCS += ulongoutRecord.c example_SRCS += floatoutRecord.c example_LIBS += $(EPICS_BASE_IOC_LIBS) USR_CPPFLAGS += -DUSE_TYPED_RSET #============================= # build an ioc application PROD_IOC += exampleDatabase # <name>_registerRecordDeviceDriver.cpp will be created from <name>.dbd exampleDatabase_SRCS += exampleDatabase_registerRecordDeviceDriver.cpp exampleDatabase_SRCS_DEFAULT += exampleDatabaseMain.cpp exampleDatabase_LIBS += exampleDatabase exampleDatabase_LIBS += example exampleDatabase_LIBS += scalarLimitRecord exampleDatabase_LIBS += powerSupplyRecord exampleDatabase_LIBS += linkRecord exampleDatabase_LIBS += helloPutGetRecord exampleDatabase_LIBS += helloRPCRecord exampleDatabase_LIBS += controlRecord exampleDatabase_LIBS += pvaClient pvDatabase qsrv pvAccessIOC pvAccess pvAccessCA nt pvData exampleDatabase_LIBS += $(EPICS_BASE_IOC_LIBS) # needed for Windows LIB_SYS_LIBS_WIN32 += netapi32 ws2_32 include $(TOP)/configure/RULES
include "base.dbd" include "PVAServerRegister.dbd" include "registerChannelProviderLocal.dbd" include "qsrv.dbd" include "pvdbcrAllRecords.dbd" include "powerSupplyRecord.dbd" include "controlRecord.dbd" include "getLinkScalarRecord.dbd" include "getLinkScalarArrayRecord.dbd" include "putLinkScalarRecord.dbd" include "putLinkScalarArrayRecord.dbd" include "helloPutGetRecord.dbd" include "helloRPCRecord.dbd" include "scalarLimitRecord.dbd" include "exampleDatabaseRegister.dbd" include "simpleBusyRecord.dbd" include "charoutRecord.dbd" include "shortoutRecord.dbd" include "ucharoutRecord.dbd" include "ushortoutRecord.dbd" include "ulongoutRecord.dbd" include "floatoutRecord.dbd"
This is where the example database is started as part of a V3 IOC.
pwd /home/install/epics/base-7.0.5/modules/exampleCPP/database/iocBoot/exampleDatabase ../../bin/linux-x86_64/exampleDatabase st.cmd
After starting enter pvdbl to see all that records. You will see all the records created by serviceMain and all the additional records created by database.
Sample configured records are available for various record types. Since epics-base does not provide scalar records of type DBF_CHAR, DBF_UCHAR, DBF_SHORT, DBF_USHORT, DBF_ULONG, or DBF_FLOAT. database/ioc provides a simplified implementation for each of these types. NOTE that the implementations do not allow attached device support.
These can be used to test:
PVDatabase implements a number of PVRecords. Record instances can be created via iocshell commands or from a non IOC application.
In the source file where You build your IOC the appInclude.dbd file must include the following:
include "base.dbd" include "PVAServerRegister.dbd" include "PVAClientRegister.dbd" include "registerChannelProviderLocal.dbd" include "qsrv.dbd" include "pvdbcrAllRecords.dbd" #...
Then in the st.cmd file you can issue help for each of the shell commands
implemented what is descibed below.
You can also create PVRecord instances.
For example:
epics> help pvdbcrScalarRecord pvdbcrScalarRecord recordName scalarType asLevel asGroup epics> pvdbcrScalarRecord PVRscalarDouble doubleThis creates a record:
pvinfo PVRscalarDouble
PVRscalarDouble
Server: 10.0.0.194:5075
Type:
structure
double value
time_t timeStamp
long secondsPastEpoch
int nanoseconds
int userTag
alarm_t alarm
int severity
int status
string message
Both create a record that has fields value, timeStamp, and alarm.
For pvdbcrScalarRecord, the type for the value field is one of the following:
boolean,byte,short,int,long,
ubyte,ushort,uint,ulong,
,float,double, or string.
For pvdbcrScalarArrayRecord the value field is an scalarArray of one of the above types.
This is a record that sets the trace level in another record in the same pvDatabase.
structure
structure argument
string recordName
int level
structure result
string status
where
This is a record that adds a record to the same pvDatabase. This can only be used to generate "soft" records, i. e. the only process support is that provided by PVRecord. This means timeStamp support.
structure
structure argument
string recordName
any union
structure result
string status
where
The following is an example:
pwd
/home/install/epics/base-7.0.5/modules/exampleCPP/exampleClient
bin/linux-x86_64/addRecord PVRaddRecord PVRdouble test
result=structure
structure result
string status success
pvinfo test
test
Server: 10.0.0.194:5075
Type:
structure
double value
time_t timeStamp
long secondsPastEpoch
int nanoseconds
int userTag
alarm_t alarm
int severity
int status
string message
This is a record that removes another record in the same pvDatabase.
structure
structure argument
string recordName
structure result
string status
where
The following shows now it can be used:
pvput PVRremoveRecord argument='{"recordName":"test"}'
Old : structure
structure argument
string recordName
structure result
string status
New : structure
structure argument
string recordName test
structure result
string status success
This is a record that processes other records in the same pvDatabase. It keeps a map of all records to process. Records can be added or removed from the list.
structure
structure argument
string command
string recordName
structure result
string status
where
This is an example that implements a record that has the following structure:
pvget -v PVRscalarLimitUbyte
PVRscalarLimitUbyte structure
ubyte value 100
time_t timeStamp <undefined>
long secondsPastEpoch 0
int nanoseconds 0
int userTag 0
alarm_t alarm
int severity 0
int status 0
string message
structure control
ubyte limitLow 0
ubyte limitHigh 250
ubyte maxStep 10
structure alarmLimit
ubyte lowAlarmLimit 20
ubyte lowWarningLimit 50
ubyte highAlarmLimit 230
ubyte highWarningLimit 200
Both serviceMain and database create record PVRscalarLimitUbyte as well
as record PVRscalarLimitDouble.
The difference is the field types.
Wherever ubyte appears in PVRscalarLimitUbyte double appears in PVRscalarLimitDouble.
After starting either serviceMain or database then in another window enter:
pvget -m -v PVRscalarLimitUbyteYou will see the above structure. Leave this running.
In another window enter
pvput PVRprocessRecord argument='{"command":"add","recordName":"PVRscalarLimitUbyte"}'
Then enter:
pvput PVRscalarLimitUbyte 1
In the window where you started monitoring You will see:
PVRscalarLimitUbyte structure
ubyte value 90
time_t timeStamp 2021-05-07 07:10:40.325
long secondsPastEpoch 1620385840
int nanoseconds 324700934
PVRscalarLimitUbyte structure
ubyte value 80
time_t timeStamp 2021-05-07 07:10:40.676
int nanoseconds 676307219
PVRscalarLimitUbyte structure
ubyte value 70
time_t timeStamp 2021-05-07 07:10:41.677
long secondsPastEpoch 1620385841
int nanoseconds 676575626
PVRscalarLimitUbyte structure
ubyte value 60
time_t timeStamp 2021-05-07 07:10:42.677
long secondsPastEpoch 1620385842
int nanoseconds 676853907
PVRscalarLimitUbyte structure
ubyte value 50
time_t timeStamp 2021-05-07 07:10:43.677
long secondsPastEpoch 1620385843
int nanoseconds 677155943
alarm_t alarm MINOR minor low alarm
int severity 1
string message "minor low alarm"
PVRscalarLimitUbyte structure
ubyte value 40
time_t timeStamp 2021-05-07 07:10:44.677
long secondsPastEpoch 1620385844
int nanoseconds 677432384
PVRscalarLimitUbyte structure
ubyte value 30
time_t timeStamp 2021-05-07 07:10:45.678
long secondsPastEpoch 1620385845
int nanoseconds 677795226
PVRscalarLimitUbyte structure
ubyte value 20
time_t timeStamp 2021-05-07 07:10:46.678
long secondsPastEpoch 1620385846
int nanoseconds 678169012
alarm_t alarm MAJOR major low alarm
int severity 2
string message "major low alarm"
PVRscalarLimitUbyte structure
ubyte value 10
time_t timeStamp 2021-05-07 07:10:47.678
long secondsPastEpoch 1620385847
int nanoseconds 678459518
PVRscalarLimitUbyte structure
ubyte value 1
time_t timeStamp 2021-05-07 07:10:48.679
long secondsPastEpoch 1620385848
int nanoseconds 678830388
Note the following:
Now enter:
pvput PVRscalarLimitUbyte 255To see why the final value is 250 instead of 255 look at control.limitHigh.
The top level Makefile and configure are typical. It has two subdirectories: scalarLimit/src and scalarLimit/src/scalarLimit. The later has the interface for code that wants to create scalarLimit records. scalarLimit/src has the implementation.
#ifndef SCALARLIMITRECORDCREATE_H
#define SCALARLIMITRECORDCREATE_H
#include <shareLib.h>
namespace epics { namespace scalarLimit {
class ScalarLimitRecord;
typedef std::tr1::shared_ptr<ScalarLimitRecord> ScalarLimitRecordPtr;
class epicsShareClass ScalarLimitRecordCreate
{
public:
/**
* @brief Create a record.
*
* @param recordName The record name.
* @param scalarType The type for the value field
* @param asLevel The access security level.
* @param asGroup The access security group.
*/
static void create(
std::string const & recordName,std::string const & scalarType,
int asLevel=0,std::string const & asGroup = std::string("DEFAULT"));
};
}}
#endif /* SCALARLIMITRECORDCREATE_H */
NOTE:
Makefile scalarLimitRecord.cpp scalarLimitRecord.dbd
TOP=.. include $(TOP)/configure/CONFIG EPICS_BASE_PVA_CORE_LIBS = pvDatabase pvAccess pvAccessCA nt pvData ca Com INC += scalarLimit/scalarLimitRecord.h DBD += scalarLimitRecord.dbd LIBRARY = scalarLimitRecord LIBSRCS += scalarLimitRecord.cpp scalarLimitRecord_LIBS += $(EPICS_BASE_PVA_CORE_LIBS) # needed for Windows LIB_SYS_LIBS_WIN32 += netapi32 ws2_32 include $(TOP)/configure/RULES
This starts with
#include <iocsh.h> #include <pv/standardField.h> #include <pv/standardPVField.h> #include <pv/timeStamp.h> #include <pv/pvTimeStamp.h> #include <pv/alarm.h> #include <pv/pvAlarm.h> #include <pv/convert.h> #include <pv/pvDatabase.h> // The following must be the last include for code scalarLimit implements #include <epicsExport.h> #define epicsExportSharedSymbols #include "scalarLimit/scalarLimitRecord.h" using namespace epics::pvData; using namespace epics::pvDatabase; using namespace std;
Next is the class definition for ScalarLimitRecord
namespace epics { namespace scalarLimit {
class epicsShareClass ScalarLimitRecord :
public epics::pvDatabase::PVRecord
{
private:
friend class ScalarLimitRecordCreate;
ScalarLimitRecord(
std::string const & recordName,epics::pvData::PVStructurePtr const & pvStructure,
int asLevel,std::string const & asGroup);
double desiredValue;
double currentValue;
public:
virtual ~ScalarLimitRecord() {}
virtual void process();
virtual bool init(){
initPVRecord();
return true;
}
};
ScalarLimitRecord::ScalarLimitRecord(
std::string const & recordName,epics::pvData::PVStructurePtr const & pvStructure,
int asLevel,std::string const & asGroup)
: PVRecord(recordName,pvStructure,asLevel,asGroup)
{}
Next is the definition of process. Only the beginning is shown. See the source code for details.
void ScalarLimitRecord::process()
{
ConvertPtr convert = getConvert();
PVStructurePtr top = this->getPVStructure();
... LOTS OF CODE
PVRecord::process();
}
Next is the code for ScalarLimitRecordCreate. Only the beginning is shown. See the source code for details.
void ScalarLimitRecordCreate::create(
std::string const & recordName,std::string const & scalarType,
int asLevel,std::string const & asGroup)
{
ConvertPtr convert = getConvert();
ScalarType st = epics::pvData::ScalarTypeFunc::getScalarType(scalarType);
FieldCreatePtr fieldCreate = getFieldCreate();
StandardFieldPtr standardField = getStandardField();
PVDataCreatePtr pvDataCreate = getPVDataCreate();
StructureConstPtr top = fieldCreate->createFieldBuilder()->
add("value",st) ->
add("timeStamp",standardField->timeStamp()) ->
add("alarm",standardField->alarm()) ->
addNestedStructure("control") ->
add("limitLow",st) ->
add("limitHigh",st) ->
add("maxStep",st) ->
endNested()->
addNestedStructure("alarmLimit") ->
add("lowAlarmLimit",st) ->
add("lowWarningLimit",st) ->
add("highAlarmLimit",st) ->
add("highWarningLimit",st) ->
endNested()->
createStructure();
... CODE to initialize fields
PVDatabasePtr master = PVDatabase::getMaster();
if(!master->addRecord(pvRecord)) {
cerr << recordName << " not added to master\n";
}
}
}}
Next is code to create an iocshell command.
static const iocshArg arg0 = { "recordName", iocshArgString };
static const iocshArg arg1 = { "scalarType", iocshArgString };
static const iocshArg arg2 = { "asLevel", iocshArgInt };
static const iocshArg arg3 = { "asGroup", iocshArgString };
static const iocshArg *args[] = {&arg0,&arg1,&arg2,&arg3};
static const iocshFuncDef scalarLimitFuncDef = {"scalarLimitRecord", 4,args};
static void scalarLimitCallFunc(const iocshArgBuf *args)
{
char *sval = args[0].sval;
if(!sval) {
throw std::runtime_error("scalarLimitRecord recordName not specified");
}
string recordName = string(sval);
sval = args[1].sval;
if(!sval) {
throw std::runtime_error("scalarLimitRecord scalarType not specified");
}
string scalarType = string(sval);
int asLevel = args[2].ival;
string asGroup("DEFAULT");
sval = args[3].sval;
if(sval) {
asGroup = string(sval);
}
epics::scalarLimit::ScalarLimitRecordCreate::create(recordName,scalarType,asLevel,asGroup);
}
static void scalarLimitRecord(void)
{
static int firstTime = 1;
if (firstTime) {
firstTime = 0;
iocshRegister(&scalarLimitFuncDef, scalarLimitCallFunc);
}
}
extern "C" {
epicsExportRegistrar(scalarLimitRecord);
}
registrar("scalarLimitRecord")
This is an example of creating a service that requires a somewhat complicated top level PVStructure.
The example has a top level pvStructure:
pvinfo PVRpowerSupply
PVRpowerSupply
Server: 10.0.0.194:5075
Type:
structure
alarm_t alarm
int severity
int status
string message
time_t timeStamp
long secondsPastEpoch
int nanoseconds
int userTag
structure power
double value
structure voltage
double value
structure current
double value
It is designed to be accessed via a channelPutGet request. The client sets power.value and voltage.value When the record processes it computes current.value. In addition the timeStamp is set to the time when process is called.
This goes into some detail because it is like all the other support modules that are used by serviceMain and database. The other support modules have a similar build structure.
The top level Makefile and configure are typical. It has a subdirectory src which has:
TOP=.. include $(TOP)/configure/CONFIG #NOTE order is important EPICS_BASE_PVA_CORE_LIBS = pvaClient pvDatabase pvAccess pvAccessCA pvData ca Com INC += powerSupply/powerSupplyRecord.h DBD += powerSupplyRecord.dbd LIBRARY = powerSupplyRecord LIBSRCS += powerSupplyRecord.cpp powerSupplyRecord_LIBS += $(EPICS_BASE_PVA_CORE_LIBS) PROD_HOST += powerSupplyClient powerSupplyClient_SRCS += powerSupplyClient.cpp powerSupplyClient_LIBS += $(EPICS_BASE_PVA_CORE_LIBS) PROD_HOST += powerSupplyMonitor powerSupplyMonitor_SRCS += powerSupplyMonitor.cpp powerSupplyMonitor_LIBS += $(EPICS_BASE_PVA_CORE_LIBS) # needed for Windows LIB_SYS_LIBS_WIN32 += netapi32 ws2_32 include $(TOP)/configure/RULESwhere:
#ifndef POWERSUPPLYRECORD_H
#define POWERSUPPLYRECORD_H
#include <pv/timeStamp.h>
#include <pv/alarm.h>
#include <pv/pvTimeStamp.h>
#include <pv/pvAlarm.h>
#include <pv/pvDatabase.h>
#include <shareLib.h>
namespace epics { namespace example { namespace powerSupply {
class PowerSupplyRecord;
typedef std::tr1::shared_ptr<PowerSupplyRecord> PowerSupplyRecordPtr;
class epicsShareClass PowerSupplyRecord :
public epics::pvDatabase::PVRecord
{
public:
POINTER_DEFINITIONS(PowerSupplyRecord);
static PowerSupplyRecordPtr create(
std::string const & recordName);
virtual ~PowerSupplyRecord() {}
virtual bool init() {return false;}
virtual void process();
private:
PowerSupplyRecord(std::string const & recordName,
epics::pvData::PVStructurePtr const & pvStructure);
void initPvt();
epics::pvData::PVDoublePtr pvCurrent;
epics::pvData::PVDoublePtr pvPower;
epics::pvData::PVDoublePtr pvVoltage;
epics::pvData::PVAlarm pvAlarm;
epics::pvData::Alarm alarm;
};
}}}
#endif /* POWERSUPPLYRECORD_H */
registrar("powerSupplyRecord")
//code that implements LIBRARY = powerSupplyRecord
#include <iocsh.h>
#include <pv/pvDatabase.h>
#include <pv/pvStructureCopy.h>
#include <pv/timeStamp.h>
#include <pv/standardField.h>
#include <pv/pvAlarm.h>
#include <pv/channelProviderLocal.h>
// The following must be the last include for code database uses
#include <epicsExport.h>
#define epicsExportSharedSymbols
#include "powerSupply/powerSupplyRecord.h"
using namespace epics::pvData;
using namespace epics::pvDatabase;
using std::string;
namespace epics { namespace example { namespace powerSupply {
PowerSupplyRecordPtr PowerSupplyRecord::create(
string const & recordName)
{
FieldCreatePtr fieldCreate = getFieldCreate();
StandardFieldPtr standardField = getStandardField();
PVDataCreatePtr pvDataCreate = getPVDataCreate();
StructureConstPtr topStructure = fieldCreate->createFieldBuilder()->
add("alarm",standardField->alarm()) ->
add("timeStamp",standardField->timeStamp()) ->
addNestedStructure("power") ->
add("value",pvDouble) ->
endNested()->
addNestedStructure("voltage") ->
add("value",pvDouble) ->
endNested()->
addNestedStructure("current") ->
add("value",pvDouble) ->
endNested()->
createStructure();
PVStructurePtr pvStructure = pvDataCreate->createPVStructure(topStructure);
PowerSupplyRecordPtr pvRecord(
new PowerSupplyRecord(recordName,pvStructure));
pvRecord->initPvt();
return pvRecord;
}
PowerSupplyRecord::PowerSupplyRecord(
string const & recordName,
PVStructurePtr const & pvStructure)
: PVRecord(recordName,pvStructure)
{
}
void PowerSupplyRecord::initPvt()
{
initPVRecord();
PVStructurePtr pvStructure = getPVStructure();
PVFieldPtr pvField;
pvField = pvStructure->getSubField("alarm");
pvAlarm.attach(pvField);
pvCurrent = pvStructure->getSubField<PVDouble>("current.value");
pvVoltage = pvStructure->getSubField<PVDouble>("voltage.value");
pvPower = pvStructure->getSubField<PVDouble>("power.value");
alarm.setMessage("bad voltage");
alarm.setSeverity(majorAlarm);
pvAlarm.set(alarm);
}
void PowerSupplyRecord::process()
{
double voltage = pvVoltage->get();
double power = pvPower->get();
if(voltage<1e-3 && voltage>-1e-3) {
alarm.setMessage("bad voltage");
alarm.setSeverity(majorAlarm);
pvAlarm.set(alarm);
throw std::runtime_error("bad voltage exception");
}
double current = power/voltage;
pvCurrent->put(current);
pvAlarm.get(alarm);
if(alarm.getSeverity()!=noAlarm) {
alarm.setMessage("");
alarm.setSeverity(noAlarm);
pvAlarm.set(alarm);
}
PVRecord::process();
}
}}}
// code that implements the iocshell command
static const iocshArg testArg0 = { "recordName", iocshArgString };
static const iocshArg *testArgs[] = {
&testArg0};
static const iocshFuncDef powerSupplyFuncDef = {
"powerSupplyRecord", 1, testArgs};
static void powerSupplyCallFunc(const iocshArgBuf *args)
{
char *recordName = args[0].sval;
if(!recordName) {
throw std::runtime_error("powerSupplyRecord invalid number of arguments");
}
epics::example::powerSupply::PowerSupplyRecordPtr record
= epics::example::powerSupply::PowerSupplyRecord::create(recordName);
bool result = PVDatabase::getMaster()->addRecord(record);
if(!result) std::cout << string(recordName) << " not added" << "\n";
}
static void powerSupplyRecord(void)
{
static int firstTime = 1;
if (firstTime) {
firstTime = 0;
iocshRegister(&powerSupplyFuncDef, powerSupplyCallFunc);
}
}
extern "C" {
epicsExportRegistrar(powerSupplyRecord);
}
Start either serviceMain or database.
pwd /home/install/epics/base-7.0.5/modules/exampleCPP/powerSupplyRecord bin/linux-x86_64/powerSupplyMonitor
pwd /home/install/epics/base-7.0.5/modules/exampleCPP/powerSupplyRecord bin/linux-x86_64/powerSupplyClient
NOTE: The client ends by requesting a voltage of 0. This results in the power supply record thowing an exception.
The build structure is similar to powerSupplyRecord.
exampleLink implements PVRecords that link to another record. Each provides two ways to access the other record:
The following records are implemented:
Start either serviceMain or database.
In another window run:
pvget -m PVRdouble PVRdoubleArray PVRgetLinkScalar PVRgetLinkScalarArray PVRputLinkScalar PVRputLinkScalarArray PVRstring PVRstringArray
Try the following:
pvput PVRputLinkScalar 1 Old : <undefined> New : 2021-03-18 13:51:46.816 1 pvput PVRputLinkScalarArray [1,2,3] Old : <undefined> [] New : 2021-03-18 13:52:23.698 [1, 2, 3]
Look at the the window where you are monitoring and see what happened.
The record being accessed must have a top level value field, which must be a scalar or scalar array.
An example of a valid record is:
pvinfo PVRdoubleArray
PVRdoubleArray
Server: 10.0.0.194:49943
Type:
structure
double[] value
time_t timeStamp
long secondsPastEpoch
int nanoseconds
int userTag
alarm_t alarm
int severity
int status
string message
putLinkScalar, putLinkScalarArray, getLinkScalar, and getLinkScalarArray all have the same basic structure. For example:
pvinfo PVRputLinkScalar
PVRputLinkScalar
Server: 10.0.0.194:49943
Type:
structure
string value
string linkRecord
string accessMethod
time_t timeStamp
long secondsPastEpoch
int nanoseconds
int userTag
alarm_t alarm
int severity
int status
string message
boolean reconnect
The only difference is if value has type string or string[].
pvput and pvget are used for examples that demonstrate how the link records work. They are all that is needed for the put records. They can also be used for the get records but if pvput changes field value and processing is succesful, value will be replaced by the value obtained from the link record. Note that exampleCPP/exampleClient has an example that just asks for a record to process.
The following is an example of how a client can specify all fields that are related to processing.
pvput PVRputLinkScalarArray '{"value":["1","2"],"linkRecord":"PVRdoubleArray","accessMethod":"client"}'
Thus a client can put values to the following:
This is actually the default for linkRecord and accessMethod when the ioc is started.
Once the desired linkRecord and accessMethod have been specified the following will work.
pvput PVRputLinkScalarArray ["100","200","300"]
For the get records the above will also work. But it is better to use exampleClient/process or the following:
pvput PVRgetLinkScalarArray '{"linkRecord":"PVRdoubleArray","accessMethod":"client"}'
When the IOC is started PVRputLinkScalar, PVRputLinkScalarArray, PVRgetLinkScalar, and gPVRetLinkScalarArray are not connected to linkRecord for either client or database access. The first time client connects PvaClientChannelPtr and PvaClientGetPtr are saved. The first time database connects PVRecordPtr is saved. Thus initialization overhead is not incured during further processing. This also means that if the client specifies linkRecord it has no effect.
There is an additional field that a client can access so that it can again put linkRecord An example is:
pvput PVRputLinkScalar reconnect="true"
Old : <undefined>
New : 2021-03-19 10:53:26.708 MINOR reconnecting
pvput PVRputLinkScalar '{"value":"a value","linkRecord":"PVRstring","accessMethod":"client"}'
Old : 2021-03-19 10:53:26.708 MINOR reconnecting
New : 2021-03-19 10:55:41.463 "a value"
pvput PVRputLinkScalar '{"value":"a value","linkRecord":"PVRstring","accessMethod":"client"}'
Old : <undefined>
New : 2021-03-19 11:20:29.381 "a value"
pvput PVRputLinkScalarArray '{"value":["a value","b value"],"linkRecord":"PVRstringArray","accessMethod":"client"}'
Old : <undefined> []
New : 2021-03-19 11:22:10.000 ["a value", "b value"]
pvput PVRputLinkScalar '{"value":"a value","linkRecord":"PVRstring","accessMethod":"database"}'
Old : 2021-03-19 11:20:29.381 "a value"
New : 2021-03-19 11:22:29.608 "a value"
pvput PVRputLinkScalarArray '{"value":["a value","b value"],"linkRecord":"PVRstringArray","accessMethod":"database"}'
Old : 2021-03-19 11:22:10.000 ["a value", "b value"]
New : 2021-03-19 11:22:49.790 ["a value", "b value"]
The build structure is similar to powerSupplyRecord.
The example implements a simple putGet that has a top level pvStructure:
structure
time_t timeStamp
long secondsPastEpoch
int nanoseconds
int userTag
structure argument
string value
structure result
string value
It is designed to be accessed via a channelPutGet request. The client sets argument.value When the record processes it sets result.value to "Hello " concatenated with argument.value. Thus if the client sets argument.value equal to "World" result.value will be "Hello World". In addition the timeStamp is set to the time when process is called.
Start either serviceMain or database.
This example requires that the server is running. In addition start a monitor as follows:
pvget -r "field()" -m PVRhelloPutGet
When the following is executed:
pwd /home/install/epics/base-7.0.5/modules/exampleCPP/helloPutGetRecord bin/linux-x86_64/helloPutGetClient
The pvget shows:
PVRhelloPutGet structure
time_t timeStamp 2021-05-15 10:25:59.502
long secondsPastEpoch 1621088759
int nanoseconds 502028194
structure argument
string value World
structure result
string value "Hello World"
A client with more functionality is also available. It can be started either before of after the server is started. It options are:
pwd /home/install/epics/base-7.0.5/modules/exampleCPP/helloPutGet bin/linux-x86_64/helloNoWaitPutGetClient -help providerName channelName request debug default pva PVRhelloPutGet "putField(argument)getField(result)" false
After it is started as follows:
bin/linux-x86_64/helloNoWaitPutGetClient
Then it accepts several commands. To see the commands just type help:
help exit putGet getPut getGet
The commands are:
The build structure is similar to powerSupplyRecord.
This is an example of a channel RPC request. Look at the source code for details but note that it simulates a RPC server that take time to process each event.
The following shows how to use the service:
pwd
/home/install/epics/base-7.0.5/modules/exampleCPP/exampleClient
bin/linux-x86_64/helloWorldRPC
_____HelloWorldRPC starting_______
_____exampleSimple___
send SimpleWorld
result
epics:nt/NTScalar:1.0
string value "Hello SimpleWorld"
_____exampleMore___
send MoreWorld
result
epics:nt/NTScalar:1.0
string value "Hello MoreWorld"
send MoreAgain
result
epics:nt/NTScalar:1.0
string value "Hello MoreAgain"
_____exampleEvenMore___
example channeRPC more
send EvenMoreWorld
response
epics:nt/NTScalar:1.0
string value "Hello EvenMoreWorld"
send EvenMoreAgain
response
epics:nt/NTScalar:1.0
string value "Hello EvenMoreAgain"
send EvenMore one more
Expected exception channel PVRhelloRPC PvaClientRPC::request request timeout
Expected exception channel PVRhelloRPC PvaClientRPC::request request aleady active
_____HelloWorldRPC done_______
The build structure is similar to powerSupplyRecord. It uses the support code from pvDatabase, which is deprecated for the following reasons:
controlRecord is support for any field that is a numeric scalar. The following must appear in the same structure as the field being supported:
control_t control
double limitLow
double limitHigh
double minStep
scalarType outputValue
where:
scalarAlarmSupport is support for any field that is a numeric scalar. The following must appear in the same structure as the field being supported:
scalarAlarm_t scalarAlarm
double lowAlarmLimit
double lowWarningLimit
double highWarningLimit
double highAlarmLimit
double hysteresis
where:
Start either serviceMain or database.
This is an iocshell command implemented in exampleCPP/support. It creates PVRecord instances that are used and described later in this tutorial. The created record uses the control and scalarAlarm support from pvDatabase.
A created record has the following structure:
pvinfo PVRcontrolDouble
PVRcontrolDouble
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.
Run the following:
pwd /home/install/epics/base-7.0.5/modules/exampleCPP/support/client/scripts ./configAll
configAll does the following:
pvget -r "control,scalarAlarm" PVRcontrolDouble
PVRcontrolDouble structure
control_t control
double limitLow -10
double limitHigh 10
double minStep 0.5
double outputValue 0
scalarAlarm_t scalarAlarm
double lowAlarmLimit -8
double lowWarningLimit -6
double highWarningLimit 6
double highAlarmLimit 8
double hysteresis 0.1
pvget -m -r "value,control.outputValue,alarm,timeStamp" -v PVRcontrolDoubleThe output from this window will be described after you enter the next command.
pvput PVRcontrolDouble 20
PVRcontrolDouble 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:
PVRcontrolDouble structure
structure control
double outputValue .5
time_t timeStamp 2021-04-06 10:39:28.987
int nanoseconds 968025553
This will continue until You see:
PVRcontrolDouble structure
structure control
double outputValue 10
time_t timeStamp 2021-04-06 10:44:50.660
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 PVRcontrolUbyte.
The scripts all appear in:
pwd /home/install/epics/base-7.0.5/modules/exampleCPP/controlRecord/client/scripts
The scripts with names that start with config can be run either interactively or via arguments. The only exception is configAll
An example usage is:
./configControl
recordName?
PVRcontrolDouble
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
It could do the same without prompts via:
./configControl PVRcontrolDouble -10 10 .5
Old : structure
control_t control
double limitLow -10
double limitHigh 10
double minStep 0.5
double outputValue 10
New : structure
control_t control
double limitLow -10
double limitHigh 10
double minStep 0.5
double outputValue 10
The same could be done using pvput as follows:
pvput -r "control" PVRcontrolDouble control='{"limitLow":"-10","limitHigh":"10","minStep":".5"}'
An example usage is:
./configScalarAlarm
recordName?
PVRcontrolDouble
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
Just like configControl it can be invoked by giving all values as arguments.
example usage is:
./configProcessRecord
processRecordName
PVRprocessRecord
command?
remove
recordName?
PVRcontrolDouble
// result is
PVRprocessRecord
argument={"command":"remove","recordName":"PVRcontrolDouble"}
Old : structure
structure argument
string command add
string recordName PVRcontrolDouble
structure result
string status success
New : structure
structure argument
string command remove
string recordName PVRcontrolDouble
structure result
string status success
It can also be invoked with arguments.
The following also works:
pvput -r "argument,result" PVRprocessRecord argument='{"command":"add","recordName":"PVRcontrolDouble"}'
Old : structure
structure argument
string command remove
string recordName PVRcontrolDouble
structure result
string status PVRcontrolDouble already present
New : structure
structure argument
string command add
spwd
tring recordName PVRcontrolDouble
structure result
string status PVRcontrolDouble already present
Convenience script for signed control limits.
#!/bin/sh ./configControl PVRcontrolDouble -10 10 .5
Convenience script for unsigned control limits.
#!/bin/sh ./configControl PVRcontrolUbyte 1 20 1
Convenience script for signed scalarAlarm limits.
#!/bin/sh ./configScalarAlarm PVRcontrolDouble -8 -6 6 8 .1
Convenience script for unsigned scalarAlarm limits.
#!/bin/sh ./configScalarAlarm PVRcontrolUbyte 2 4 16 18 1
Use of this was shown above.
#!/bin/bash ./controlSigned ./scalarAlarmSigned ./controlUnsigned ./scalarAlarmUnsigned ./configProcessRecord PVRprocessRecord add PVRcontrolDouble ./configProcessRecord PVRprocessRecord add PVRcontrolUbyte
Records PVRcontrolDouble and PVRcontrolUbyte both use the control and scalarAlarm record support from pvDatabaseCPP.
Previously there was an example accessing PVRcontrolDouble. Now lets look at an example accessing PVRcontrolUbyte.
After starting the database and running configAll, look at:
pvget -r "control,scalarAlarm" PVRcontrolUbyte
PVRcontrolUbyte 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:
pvget -r "value,alarm,timeStamp,control.outputValue" -v -m PVRcontrolUbyte
Keep this window and see what happens when you run the following in another window:
pvput PVRcontrolUbyte 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.