NOTE: This is the working version of the developerGuide.
This document is a developer guide for the module components of EPICS-7
This document briefly describes a set of application programming interfaces (APIs) for PVA.
These core APIs provide a toolkit for creating applications that access and/or provide sources of structured data. This document is intended for EPICS software developers who want to implement applications via the core APIs.
This document is a tutorial rather then a detailed reference manual. Links to the reference manual for each PVA modules are provided in the following overview sections.
The current release of EPICS 7 includes the following PVA modules:
In addition the following, although not part of the current EPICS 7 release, are based on PVA:
pva2pva and pvaPy are implemented only in C++. Each has a single github repository.
All the components are implemented in C++. Some of the components are also implemented in Java, but the Java components are not described here.
All components have it's own public repository. For example pvDataCPP has the repository pvDataCPP
pvData supports structured data where a structure is a set of fields. Each field is composed of one of the following types:
Each normative type defines a structure that has a set of standard fields. For example NTScalar defines:
epics:nt/NTScalar:1.0 double value // mandatory and can be any scalar type string descriptor // optional alarm_t alarm // optional int severity int status string message time_t timeStamp // optional long secondsPastEpoch int nanoseconds int userTag display_t display // optional double limitLow double limitHigh string description string format string units control_t control // optional double limitLow double limitHigh double minStep string extra1 // extra string[] extra2 //
A client written in either Java or C++ can communicate with a server written in either Java or C++. All communication between client and server is done via the methods provided by pvAccess and by pvData objects.
pvAccess provides:
channelPutGet and channelRPC provide the equivalent of a Remote Procedure Call. The client passes a pvData object to the server. This pvData object is the argument for the RPC. The server uses this to decide what to do and sends a pvData object back to the client, which is the RPC result.
pvaClient is a synchronous wrapper for the pvAccess API, which is a callback based API. In addition pvaClient provides many convenience methods. Thus it is easier to use than pvAccess itself.
pvaClientCPP also provides simplified callback classes, which can be used to implement non blocking client code.
A framework for implementing a network accessible database of smart memory resident records. Network access is via pvAccess. The data in each record is a top level PVStructure as defined by pvData. A complete implementation of ChannelProvider, named local, is provided for accessing the records. The framework can be extended in order to create record instances that implement services. The minimum that an extension must provide is a top level PVStructure, a record name, and a process method. The record name is the channel name for provider local.
qsrv (a major component of pva2pva) is a channel provider for accessing DBRecords in an IOC. qsrv allows clients to get, put and monitor V3 PVs (fields of EPICS DB records) via pvAccess, translating the value and its meta data (graphics limits, alarm status, timestamp) to or from V4 Normative Type (NT) pvData structures (NTScalar, NTScalarArray, and NTEnum).
A Python wrapper for pvData and pvAccess. This is not discussed in this document.
The basic requirements for a channel provider are:
pvAccess implements channelProviderRegistry, which allows an arbitrary number of providers.
Starting with EPICS 7 there are separate client and server registries.
A provider is one of the following:
Before either kind of provider can be used it must first register itself with the channelProviderRegistry
An important component of EPICS V4 is provider pva, which is a provider that implements a connection between a client and a server that uses the pva network protocol.
Provider pva has two components:
Network communcation is used between client and server. Remote pva transfers data between the network and channel providers that have registered at the remote node.
An arbitrary number of client providers can be implemented. Each provider must implement the channel interface mentioned above and must register with the channelProviderRegistry before it can be used by a client.
pvAccess itself provides the following client providers:
At present the following providers are available for the remote side of pva
At present a client that is not running as part of a V3 IOC or a pvDatabase has two choices for channelProvider: pva and ca. A client running as part of a V3 IOC also has the same choices.
Since channelProviderRegistry allows an arbitrary number of providers, additional providers can be developed for either the client or remote side of pva.
pvAccessCPP implements the following command line tools: pvlist, pvget, pvput, pvinfo, and eget.
See: pvTools
pvget and pvput support request options. See: pvRequest
NOTE: exampleCPP is not part of the EPICS 7 release. It must be cloned from github:
git clone https://github.com/epics-base/exampleCPP.git
exampleCPP has examples that use code from all modules described in this document except for pvaPy. In particular it has examples:
These examples can be used while learning what is described in this document. ExampleCPP has other examples.
Examples in exampleClient require that the database in exampleCPP/database or exampleCPP/serviceMain is started. The exampleDatabase is started as part of a V3 IOC. It has PVRecords and V3 DBRecords and starts qsrv. All the V4 PVRecords have the prefix PVR and all the V3 DBRecords have the prefix DBR.
In linux exampleCPP/serviceMain can be started as follows:
pwd /home/epicsv4/master/exampleCPP/serviceMain bin/linux-x86_64/serviceMain
In linux exampleCPP/database can be started as follows:
pwd /home/epicsv4/master/exampleCPP/database/iocBoot/exampleDatabase ../../bin/linux-x86_64/exampleDatabase st.cmd
The example database has both V3 IOC records and V4 PVRecords. In addition qsrv is running. Thus all V3 records are available via either ca or pva. The PVRecords are only available via pva. Examples of using ca and pva command line tools are:
pvlist GUID 0x7F06B2560000000047B97D25 version 1: tcp@[10.0.0.37:45345, 192.168.124.1:45345] pvlist 0x7F06B2560000000047B97D25 DBRao01 DBRdouble00 .... many more records pvget PVRlong PVRlong 0 caget PVRlong Channel connect timed out: 'PVRlong' not found. caget DBRdouble01 DBRdouble01 0 pvget DBRdouble01 DBRdouble01 0 caget PVRushort01 Channel connect timed out: 'PVRushort01' not found. pvget PVRushort01 PVRushort01 0
The examples in exampleCPP/exampleClient can now be run. For example:
pwd /home/epicsv4/masterCPP/exampleCPP/exampleClient ls bin/linux-x86_64/ addRecord getField ntMultiGet examplePvaClientGet getputmonitor ntMultiMonitor examplePvaClientMonitor helloWorldPutGet ntMultiPut examplePvaClientMultiDouble helloWorldRPC process examplePvaClientNTMulti monitor put examplePvaClientProcess multiGetDouble putGet examplePvaClientPut multiMonitorDouble putUnion get multiPutDouble rpc bin/linux-x86_64/examplePvaClientGet _____examplePvaClientGet starting_______ __exampleDouble__ short way as double 0 long way as double 0 ... LOTS MORE OUTPUT
EPICS V3 DBD defines data related to the VAL field of a DBRecord
The following provide pvData definitions for this data: alarm, timeStamp, enum, display, control, and alarmLimit.
Each is a structure that also has an associated structure id name. For example alarm_t is the structure id name for an alarm structure. For the tutorial alarm, timeStamp, and enum are briefly discussed. Below, in section "Special Fields", there is a fuller description of these and the other definitions. Also see: pvRequest
alarm_t alarm int severity int status string message
pvData provides code associated with a alarm structure. This code restricts severity to be one of:
noAlarm,minorAlarm,majorAlarm,invalidAlarm,undefinedAlarm
and status to be one of:
noStatus,deviceStatus,driverStatus,recordStatus, dbStatus,confStatus,undefinedStatus,clientStatus
time_t timeStamp long secondsPastEpoch int nanoseconds int userTag
pvData provides code associated with a timeStamp structure. This code provides support that makes the timeStamp UTC (Universial Time Coordinated) compliant.
enum_t value int index string[] choices
pvData provides code associated with an enum structure. This forces the index to select an element of the choices.
Each of the PVChannel create methods has a PVStructure argument named pvRequest.
This structure allows the client to 1) select a subset of the fields in the top level structure
from the server, 2) provide record options, and 3) provide field specific options.
See:
pvRequest
for a full description.
pvAccess provides a method createRequest that, given a string, creates a pvRequest structure.
For this tutorial lets just give a simple example:
"field(value, alarm, timeStamp)" or just "value, alarm, timeStamp"Specifies that the client wants to receive the top level fields named value, alarm , and timeStamp.
The tutorial is not part of an EPICS 7 release. It can be cloned via the command:
git clone https://github.com/mrkraimer/pvaClientTutorialCPP.git cd pvaClientTutorialCPP make
Clone it in the same directory that holds your EPICS 7 release. Documentation is provided in pvaClientTutorialCPP/documentation/clientTutorialCPP.html.
NOTES:
pvAccess implements the following providers:
Providers can be provided for other data sources. A provider must implement ChannelProvider and Channel. pvAccess supports an arbitrary number of providers. On the server side of remote pva providers must be implemented, because pva calls the providers to implement the Channel methods.
At present C++ provides two server side providers:
The following discusses the following fields:
pvDataCPP/include/pv/pvEnumerated.h
An enumerated structure is a structure that has fields:
enum_t int index string[] choices
PVEnumerated bool attach(PVField pvField) void detach() boolean isAttached() boolean setIndex(int index) int getIndex() String getChoice() boolean choicesMutable() String[] getChoices() int getNumberChoices() bool setChoices(String[] choices)
where
pvDataCPP/include/pv/alarm.h and pvDataCPP/include/pv/pvAlarm.h
An alarm structure is defined as follows:
alarm_t alarm int severity int status string message
Note that severity and status are NOT defined as enumerated structures. The reason is performance, i. e. prevent passing the array of choice strings everywhere. The AlarmStatus and AlarmSeverity provide the equivalent of choices for an enumerated structure.
Alarm Severity defines the possible alarm severities
enum AlarmSeverity{ noAlarm,minorAlarm,majorAlarm,invalidAlarm,undefinedAlarm } AlarmSeverityFunc { AlarmSeverity getSeverity(int value) String[] getSeverityNames() }where
Alarm Status defines the possible alarm status conditions
enum AlarmStatus { noStatus,deviceStatus,driverStatus,recordStatus, dbStatus,confStatus,undefinedStatus,clientStatus } AlarmStatusFunc{ getStatus(int value) String[] getStatusNames()where
Alarm { String getMessage() void setMessage(String message) AlarmSeverity getSeverity() void setSeverity(AlarmSeverity alarmSeverity) AlarmStatus getStatus() void setStatus(AlarmStatus alarmStatus) }where
PVAlarm { bool attach(PVField pvField) void detach() boolean isAttached() void get(Alarm alarm) bool set(Alarm alarm) }
where
pvDataCPP/include/pv/timeStamp.h and pvDataCPP/include/pv/pvTimeStamp.h
A timeStamp is represented by the following structure
time_t timeStamp long secondsPastEpoch int nanoseconds int userTag
The Epoch is the POSIX epoch, i.e. Jan 1, 1970 00:00:00 UTC. Both the seconds and nanoseconds are signed integers and thus can be negative. Since the seconds is kept as a 64 bit integer, it allows for a time much greater than the present age of the universe. Since the nanoseconds portion is kept as a 32 bit integer it is subject to overflow if a value that corresponds to a value that is greater than a little more than 2 seconds or a little less than -2 seconds. The support code always adjust seconds so that the nanoSecconds part is normalized, i. e. it has is 0<=nanoseconds<nanoSecPerSec..
The definition of TimeStamp is:
TimeStamp { void normalize() void fromTime_t(time_t time) void toTime_t(time_t time long getSecondsPastEpoch() long getEpicsSecondsPastEpoch() int getNanoseconds() int getUserTag() void setUserTag(int userTag) void put(long secondsPastEpoch,int nanoseconds) long getMilliSeconds() void put(long milliSeconds) void getCurrent() double toSeconds() boolean equals(TimeStamp other) boolean lt(TimeStamp other) boolean le(TimeStamp other) void add(long seconds) void add(double seconds) double diff(TimeStamp a,TimeStamp b) }
where:
The TimeStamp class provides arithmetic and comparison methods for time stamps. The result is always kept in normalized form, which means that the nanosecond portion is 0<=nano<nanoSecPerSec. Note that it is OK to have timeStamps for times previous to the epoch.
PVTimeStamp { bool attach(PVField pvField) void detach() bool isAttached() void get(TimeStamp timeStamp) boolean set(TimeStamp timeStamp) }where
pvDataCPP/include/pv/display.h and pvDataCPP/include/pv/pvDisplay.hNOTE: Should format be replaced by precision and form?
Display information is represented by the following structure
structure display double limitLow double limitHigh string description string format string units
Display { double getLow() double getHigh() void setLow(double value) void setHigh(double value) String getDescription() void setDescription(String value) String getFormat() // broken void setFormat(String value) // broken String getUnits() void setUnits(String value) }
where
PVDisplay { bool attach(PVField pvField) void detach() bool isAttached() void get(Display display) bool set(Display display) }
where
pvDataCPP/include/pv/control.h and pvDataCPP/include/pv/pvControl.hNOTE: Should minStep be named maxStep?
Control information is represented by the following structure
structure control double limitLow double limitHigh double minStep
Control{ double getLow() double getHigh() double getMinStep() void setLow(double value) void setHigh(double value) void setMinStep(double value) }
where
PVControl boolean attach(PVField pvField) void detach() boolean isAttached() void get(Control control) boolean set(Control control)
where
NOTE: Not sure if this is being used by anyone.
Defined in:pvDataCPP/include/pv/standardField.h and pvDataCPP/include/pv/standardPVField.h
For each numeric scalar type the following is defined:
boolean active scalarType lowAlarmLimit scalarType lowWarningLimit scalarType highWarningLimit scalarType highAlarmLimit int lowAlarmSeverity int lowWarningSeverity int highWarningSeverity int highAlarmSeverity scalarType hysteresis
For boolean the following is defined:
boolean active int falseSeverity int trueSeverity int changeStateSeverity
For enum the following is defined:
boolean active int stateSeverity int changeStateSeverity
The introspection interface for every field has an ID, which is available via method:
class Field { ... string getID(); ... };
This section describes how the IDs are assigned.
boolean byte short int long ubyte ushort ulong float double string
boolean[] byte[] short[] int[] long[] ubyte[] ushort[] ulong[] float[] double[] string[]
any // variant union union // restricted union
any[] union[]
structure
alarm_t time_t display_t control_t enum_t valueAlarm_t
Like structure except that [] is appended.
pvDataCPP has a Convert Facility. The faclity only supports complete array copies between two scalar arrays of the same type and only does a shallow copy. It also does a shallow copy for union, unionArray, and structureArray fields. It also has a separate facility that does sub-array copies between two arrays of the same type.
This section describes the supported conversions between data types.
interface Convert { void getString(StringBuilder buf,PVField pv, int indentLevel); void getString(StringBuilder buf,PVField pv); void fromString(PVScalar pv,String from); void fromString(PVScalarArray pv,String from); int fromStringArray(PVScalarArray pv, int offset, int len, String[]from, int fromOffset); int toStringArray(PVScalarArray pv, int offset, int len, String[]to, int toOffset); // For the following the pv Type must be PVByte, ...., PVDouble int8 toByte(PVScalar pv); int16 toShort(PVScalar pv); int32 toInt(PVScalar pv); int64 toLong(PVScalar pv); uint8 toUByte(PVScalar pv); uint16 toUShort(PVScalar pv); uint32 toUInt(PVScalar pv); uint64 toULong(PVScalar pv); float toFloat(PVScalar pv); double toDouble(PVScalar pv); String toString(PVScalar pv); void fromByte(PVScalar pv, int8 from); void fromShort(PVScalar pv, int16 from); void fromInt(PVScalar pv, int32 from); void fromLong(PVScalar pv, int64 from); void fromUByte(PVScalar pv, uint8 from); void fromUShort(PVScalar pv, uint16 from); void fromUInt(PVScalar pv, uint32 from); void fromULong(PVScalar pv, uint64 from); void fromFloat(PVScalar pv, float from); void fromDouble(PVScalar pv, double from); }
The array methods all return the number of elements copied or converted. This can be less than len if the PVField array contains less than len elements.
newLine is a convenience method for code that implements toString It generates a newline and inserts blanks at the beginning of the newline.
The getString methods dump the data in the metadata syntax described in the pvData project overview. Note that the toString methods of PVField are implemented by calling these convert methods.
class Convert; typedef std::tr1::shared_ptr<Convert> ConvertPtr; class Convert { public: static ConvertPtr getConvert(); ~Convert(); void copy(PVFieldPtr const & from, PVFieldPtr const & to); void getString(std::string * buf,PVFieldPtr const & pvField,int indentLevel); void getString(std::string * buf,PVFieldPtr const & pvField); std::size_t fromString( PVStructurePtr const &pv, StringArray const & from, std::size_t fromStartIndex = 0); void fromString(PVScalarPtr const & pv, std::string const & from); std::size_t fromString(PVScalarArrayPtr const & pv, std::string const &from); std::size_t fromStringArray( PVScalarArrayPtr const & pv, std::size_t offset, std::size_t length, StringArray const & from, std::size_t fromOffset); std::size_t toStringArray(PVScalarArrayPtr const & pv, std::size_t offset, std::size_t length, StringArray & to, std::size_t toOffset); int8 toByte(PVScalarPtr const & pv); int16 toShort(PVScalarPtr const & pv); int32 toInt(PVScalarPtr const & pv); int64 toLong(PVScalarPtr const & pv); uint8 toUByte(PVScalarPtr const & pv); uint16 toUShort(PVScalarPtr const & pv); uint32 toUInt(PVScalarPtr const & pv); uint64 toULong(PVScalarPtr const & pv); float toFloat(PVScalarPtr const & pv); double toDouble(PVScalarPtr const & pv); std::string toString(PVScalarPtr const & pv); void fromByte(PVScalarPtr const & pv,int8 from); void fromShort(PVScalarPtr const & pv,int16 from); void fromInt(PVScalarPtr const & pv, int32 from); void fromLong(PVScalarPtr const & pv, int64 from); void fromUByte(PVScalarPtr const & pv,uint8 from); void fromUShort(PVScalarPtr const & pv,uint16 from); void fromUInt(PVScalarPtr const & pv, uint32 from); void fromULong(PVScalarPtr const & pv, uint64 from); void fromFloat(PVScalarPtr const & pv, float from); void fromDouble(PVScalarPtr const & pv, double from); } static inline ConvertPtr getConvert() { return Convert::getConvert(); }
NOTE: PVField and derived classes provide method:
void copy(const PVField& from);
This supports sub-array copying between arrays that have the same type.
template<typename T> void copy( PVValueArray<T> & pvFrom, size_t fromOffset, size_t fromStride, PVValueArray<T> & pvTo, size_t toOffset, size_t toStride, size_t count); void copy( PVScalarArray & from, size_t fromOffset, size_t fromStride, PVScalarArray & to, size_t toOffset, size_t toStride, size_t count); void copy( PVStructureArray & from, size_t fromOffset, size_t fromStride, PVStructureArray & to, size_t toOffset, size_t toStride, size_t count); void copy( PVArray & from, size_t fromOffset, size_t fromStride, PVArray & to, size_t toOffset, size_t toStride, size_t count);
The last copy is the only one most client need to call. It either throws an error if the element types do not match or calls the other copy functions. The arguments are:
An exception is thrown if: