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 statuswhere
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 statuswhere
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 statuswhere
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 statuswhere
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 200Both 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 678830388Note 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 outputValuewhere:
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 hysteresiswhere:
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 hysteresisexample 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 1Look 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.