EPICS pvRequest

2019.04.19

editors:
Marty Kraimer

Abstract

Each create method of class Channel, in pvAccess, has an argument PVStructure pvRequest. This document discusses how this argument is defined and used.

This product is available via an open source license

Table of Contents

Introduction

pvAccess: pvRequest argument

pvAccess defines interface Channel, which is a connection between a client and a server. Channel has a number of create methods. Each has an argument pvRequest:

NOTE: The following is pseudo code.
ChannelProcess createChannelProcess(
    ChannelProcessRequester channelProcessRequester,
    PVStructure pvRequest);
ChannelGet createChannelGet(
    ChannelGetRequester channelGetRequester,
    PVStructure pvRequest);
ChannelPut createChannelPut(
    ChannelPutRequester channelPutRequester,
    PVStructure pvRequest);
ChannelPutGet createChannelPutGet(
    ChannelPutGetRequester channelPutGetRequester,
    PVStructure pvRequest);
ChannelRPC createChannelRPC(
    ChannelRPCRequester channelRPCRequester,
    PVStructure pvRequest);
ChannelArray createChannelArray(
    ChannelArrayRequester channelArrayRequester,
    PVStructure pvRequest);
Monitor createMonitor(
    MonitorRequester MonitorRequester,
    PVStructure pvRequest);

createRequest

pvAcccess does not define the format of a pvRequest but pvData provides a convenience method:

PVStructure createRequest(String request);
This creates a pvRequest that can be used to communicate with a server.

pvDataCPP and pvDataJava both implement CreateRequest.

A pvRequest structure allows a client to select:

an arbitrary set of fields.
When a client connects to channel, it receives a Structure that describes the PVStructure the channel supports. From the Structure, or just by anticipating what the server supports, the client can select the fields it wants to use.
global and field specific options
The client can pass global and field specific options to the server.
An option is just a name,value pair, where both name and value are a string.

terminology

channel access
This refers to both the client API and network protocol that has existed since the last half of the 1980's.
It is used by clients to communicate with the runtime database of DBRecords in an IOC.
pva
This is the V4 channel provider. This includes:
network protocol
A network protocol that passes PVStructure data between client and server.
client API
An API for clients
provider API
An API for use by channel providers. A provider can exist on either the client or server.
implementation
A provider for both client and server side of the network protocol is implemented.
ca
This is a client side provider that uses the channel access network protocol.
qsrv
This is a server side provider that provides access to DBRecords.

brief history of pvRequest

In 2006 a project named javaIOC was started by Marty Kraimer. Shortly after Matej Sekoranja also started working on this project. Until about 2000, they were the only javaIOC developers.

The pvAccess API was one of the first projects. How to pass client requests to the server was a big problem. Then it was agreed that, since pvData supports structured data, a single argument can be used to pass client requests to the server, i.e.

PVStructure pvRequest

pvRequest allows a client to pass structured data to the server. But without some additional facility, clents, servers, and tools would not know what to do with a pvRequest structure. Thus a facility CreateRequest was created. It takes a request string and creates a pvRequest.

In about 2000 work started on a C++ implementation of what was in the javaIOC. At this time more people became involved with develpment. This is now what is called the EPICS 7 V4 modules. Both Java, C++, and Python implementations are now provided.

client guidelines

When a client connects to a channel, the client callback has a Structure argument. The Structure is the introspection interface for the PVStructure associated with the channel. The client can use Structure to select the fields it wants to get, put, monitor, etc.

Another way is for the client to guess which fields the channel supports. This is what most clients will do. For example a widget for a display manager knows which fields it requires.

The server decides which fields it supports. It ignores fields that client requests but are not supported. When a get, put, etc, connects the client code must be prepared to accept what the server provides.

server guidelines

A server only provides fields it supports for the operation, e. g. get, put. If it does not support any fields the client requests then it should return an error.

The server looks for record and field options it supports, and just ignores other options.

Goals

pvAccess provides the ability to pass structured data between clients and servers. Since the data can be quite complex it could be very difficult for clients to use. Lets consider a few typical types of clients.

client tools

Tools like CSS, caqtDM, etc., provide widgets like strip chart , alarm display, slider, etc. These widgets want data like EPICS base provides. That means some combination of value, alarm, timeStamp, display, and control information.

channelPutGet services

A service that is implemented via a channelPutGet request could provide data via a structure like:

structure recordName
    structure argument
       // details are service specific
    structure result
       // details are service specific
where
argument
Data sent by client
result
Data returned to the client from the service.

In this case the client could just say that it wants "putField(argument),getField(result)"

normative type support

There is support for some normative types like NTScalar and NTArray. Other normative types could provide helper code that either uses the conventions and support described in this document or invent their own support for creating a pvRequest.

other specialized support

For specialized support it is always possible to create private conventions for pvRequest. But then general pupose tools will be much less useful and it may also be necessary for the support to implement various Channel methods.

Create Request

Oveview

The following are the two main components related to pvRequest:

CreateRequest
PVStructure createRequest(String request);
Creates a "PVStructure pvRequest" to be passed from a client to a server. A pvRequest structure allows a client to select:
  1. An arbitrary subset of the fields in the top level data structure for the channel.
  2. Global and field specific options
This is implemented in pvDataJava and pvDataCPP.
PVCopy
PVCopy is a facility that copies data between a top level PVStructure for a client and the top level PVStructure from the server.

It uses the pvRequest from the client to create the client top level PVStructure.

PVCopy is used by the server side of pvAccess to transfer data between the two top level PVStructures.

In Java this is implemented in pvDataJava. In C++ it is implemented in pvDatabaseCPP.

purpose

The purpose is to create a pvRequest structure for accesssing data in a server. It allows the clients to select an arbitrary subset of the fields in the top level structure associated with the channel. It also allows the client to specify options. Thus the client can specify:

desired fields
An arbitrary set of fields can be specified.
global options
Global options are options that apply to the record itself.
field options
These are options that apply to a field.

NOTE: The term record is adapted from pvDatabase. A pvDatabase is database of a memory resident smart records. A pvAcccess channel is a connection to a record. A record has a top level PVStructure that holds the data for the record.

Other pvAcccess providers must provide a PVStructure for each channel it supports.

qsrv, which provides access to iocCore DBRecords, is an example that also accepts a pvRequest created by a call to createRequest.

Relationship to Channel methods of pvAccess

Channel has methods:

createChannelGet,createChannelPut,and createMonitor
For these the request string is of the form:
record[option,...]field(fieldDef,...)
OR
field(fieldDef,...)
OR
fieldDef,...
createChannelPutGet
For these the request string is of the form:
record[option,...]putField(fieldDef,...)getField(fieldDef,...)
OR
putField(fieldDef,...)getField(fieldDef,...)
createChannelProcess
Server dependent. Provider local accepts an empty string.
createChannelRPC
Server dependent.
createChannelArray
Server dependent. Provider local accepts an empty string or a string which is the field name.

syntax

This section provides an easy (hopefully) to understand description.

A request argument has the syntax:

record[option,...]field(fieldDef,...)
OR
field(fieldDef,...)
OR
fieldDef,...
OR
record[option,...]putField(fieldDef,...)getField(fieldDef,...)
OR
putField(fieldDef,...)getField(fieldDef,...)

NOTES:

Thus a request consists of record options and sets of field definitions or just field definitions. A record option is of the form:

record[name=value,...]

A field(...),getField(...),putField(...) is a comma separated set of fieldDefs which are of the form:

fullFieldName
or
fullFieldName[option,...]
or
fullFieldName{fieldDef,...}     // recursive definition
or
fullFieldName[option,...]{fieldDef,...}     // recursive definition

fullFieldName selects a subfield of the current sub-structure being accessed. Initially this means the top level structure of the data associated with the channel. In a recursive definition the current sub-structure becomes the location asscociated with fullFieldName

If fieldName{fieldDef,...} is given then the generated data structure will have a structure field with subfields.

The syntax was chosen to be easy to use and to parse:

record[...]
Specifies a set of global options, i. e., options that apply to the record itself.
field(...)
putField(...)
getField(...)
Each selects a set of subfields of the top level structure. Each specifies a comma separated set of fieldDefs.
fieldDef
Selects a single subfield of the current structure.
option
A name=value pair. Both name and value are character strings.
[...]
Holds a comma separated set of options.
{...}
Selects a set of subfields of a substructure within the top level structure. Each defines a comma separated set of fieldDefs. This is a recursive definition. Thus a fullFieldName within {} is relative to structure that is referenced by {}.

naming conventions

blanks
All blanks are removed before parsing is started.
reserved characters
The following characters may not be used except as used in the above syntax:
{ } ( ) [ ] = ,

The character '.' in a fieldDef separates field names.
field name
A field name can only contain alphanumeric characters and the character '_', but must not start with the character '_'.
This is not enforced by the current parser but should be a rule for field names.
option name
This should follow the same convertion as field name.
This is not enforced by the parser but future changes could.
option value
For now the only illegal characters are the reserved characters.
Alphanumeric characters are valid.
The characters ':' and '?' are also valid.
What other characters should be allowed?

Thus a request consists of record options and sets of field definitions or just field definitions.
A record option is of the form:

record[name=value,...]

field,putFeld, and getField each contain a comma separated set of fieldDefs which are of the form:

fullFieldName
or 
fullFieldName[option,...]
or 
fullFieldName{request}     // recursive definition

A fullFieldName is the full name of a field in the PVRecord.

If request is null or an empty string than an entire structure is selected.

examples

simple example

mrk> pvget -r "" -v PVRdouble   // "" means entire PVStructure
PVRdouble
epics:nt/NTScalar:1.0 
    double value 0
    alarm_t alarm
        int severity 0
        int status 0
        string message 
    time_t timeStamp
        long secondsPastEpoch 0
        int nanoseconds 0
        int userTag 0
mrk> pvget -r "value" -v PVRdouble
PVRdouble
structure 
    double value 0
mrk> pvget -r "record[process=true]field(value,timeStamp)" -v PVRdouble
PVRdouble structure 
    double value 0
    time_t timeStamp 2019-04-17 13:18:55.617  
        long secondsPastEpoch 1555521535
        int nanoseconds 617126872
        int userTag 0

more complex example

Assume that the channel has the following data:

pvget -r "field()" -v PVRdumbPowerSupply
PVRdumbPowerSupply structure 
    alarm_t alarm 
        int severity 0
        int status 0
        string message 
    time_t timeStamp <undefined>              
        long secondsPastEpoch 0
        int nanoseconds 0
        int userTag 0
    structure power
        double value 0
        alarm_t alarm 
            int severity 0
            int status 0
            string message 
    structure voltage
        double value 0
        alarm_t alarm 
            int severity 0
            int status 0
            string message 
    structure current
        double value 0
        alarm_t alarm 
            int severity 0
            int status 0
            string message 
Then the following selects a subset of the fields:
mrk> pvget -r "field(alarm{severity,message},timeStamp.secondsPastEpoch,power)" -v PVRdumbPowerSupply
PVRdumbPowerSupply structure 
    structure alarm
        int severity 0
        string message 
    structure timeStamp
        long secondsPastEpoch 0
    structure power
        double value 0
        alarm_t alarm 
            int severity 0
            int status 0
            string message 

pvRequest compliance

pvDatabase

pvDatabaseCPP and pvDatabaseJava are compliant with pvRequest including record options.

As described below pvDatabaseCPP supports field options via plugin support.

The latest epics-base implements plugin support, but not via pvRequest.

ca provider

ca is a client provider that uses the channel access network protocol, but presents data to the client as pvData structures.

The latest version of ca in pvAccessCPP has pvRequest support that allows the client to select a combination of of value, alarm, timeStamp, display, control, and valueAlarm information.

It implements record options, but not field options. However the client can use the epics-base plugin support.

qsrv

qsrv is not compliant with pvRequest. It does honor the record options, but does not honor the field subset options. It does honor the epics-base plugin support.

For example:

mrk> pvget -p ca -r "value" -v DBRdouble
DBRdouble
structure 
    double value 0
mrk> pvget -p pva -r "value" -v DBRdouble
DBRdouble
epics:nt/NTScalar:1.0 
    double value 0
    alarm_t alarm
        int severity 3
        int status 2
        string message UDF
    time_t timeStamp
        long secondsPastEpoch 631152000
        int nanoseconds 0
        int userTag 0
    display_t display
        double limitLow -10
        double limitHigh 10
        string description 
        string format 
        string units volts
    control_t control
        double limitLow -9
        double limitHigh 9
        double minStep 0
    valueAlarm_t valueAlarm
        boolean active false
        double lowAlarmLimit -8
        double lowWarningLimit -6
        double highWarningLimit 6
        double highAlarmLimit 8
        int lowAlarmSeverity 0
        int lowWarningSeverity 0
        int highWarningSeverity 0
        int highAlarmSeverity 0
        double hysteresis 0

Existing record options

At present the following record options are supported:

queueSize
This is used to define the queueSize for monitors. The default is:
record[queueSize=2]
A larger size can be specified.
process
This is used by qsrv and pvDatabase to specify if records should be processed. The default is false for channelGet and true for channelPut and channelPutGet.
An example is:
record[process=false]
block
This is used to specify if a request to process a record should block until the record completes processing. The default is the same as the value of the process option. An example is:
record[block=false]

pvDatabase field plugin options

For details see: pluginSupport

For example:

mrk> pvget -r "value" PVRdoubleArray
PVRdoubleArray
structure 
    double[] value [1,2,3,4,5,6,7,8,9,10]
mrk> pvget -r "value[array=3:5]" PVRdoubleArray
PVRdoubleArray
structure 
    double[] value [4,5,6]

iocCore and qsrv plugin options

Beginning with the 3.15 releases of epics-base channel plugin filters are supported. Currently there is support for TimeStamp, Deadband, Array, and Synchronize. In addition code can be written to support other plugins without requiring any changes to epics-base.

Using the channel access client API, the client requests plugins by appending to the channel name.
For example:

mrk> caget DBRdoubleArray
DBRdoubleArray 5 1 2 3 4 5
mrk> caget DBRdoubleArray.[2:4]
DBRdoubleArray.[2:4] 3 3 4 5
mrk> pvget DBRdoubleArray.[2:4]
DBRdoubleArray.[2:4] 2019-04-17 13:49:48.919  [1,2,3]   | BUG IN QSRV??
mrk> pvget -p ca DBRdoubleArray.[2:4]
DBRdoubleArray.[2:4] [3,4,5]

Some observations about the channel filters:

channel access does not provide for client options
The client API allows the client to choose one of the DBR_... data types. It does not provide for client specific options.
client specifies filter by appending to the channel name
Since the client API does not provide client specific options, this is the only way to specify options.
channel filters only make sense for value field
Filters only make sense for field VAL, which is the default field if no field name is specified.
current implementation does not connect if client gives an incorrect request
mrk> caget DBRdoubleArray.[2:4]
DBRdoubleArray.[2:4] 3 3 4 5
mrk> caget DBRdoubleArray.[xxx:yyy]
Channel connect timed out: 'DBRdoubleArray.[xxx:yyy]' not found.
current implementation does not work for put
mrk> pvput DBRdoubleArray [1,2,3,4,5]
Old : 2019-04-15 14:28:49.283  [100,200]
New : 2019-04-15 14:31:15.022  [1,2,3,4,5]
mrk> caget DBRdoubleArray.[2:4]
DBRdoubleArray.[2:4] 3 3 4 5
mrk> caput DBRdoubleArray.[2:4] 3 10 20 30
Old : DBRdoubleArray.[2:4] 3 3 4 5
New : DBRdoubleArray.[2:4] 3 3 0 0
mrk> caget DBRdoubleArray
DBRdoubleArray 5 3 0 0 0 0
mrk> pvput DBRdoubleArray [1,2,3,4,5]
Old : 2019-04-18 05:51:24.438  [100,200,300]
New : 2019-04-18 05:52:30.546  [1,2,3,4,5]
mrk> pvput DBRdoubleArray.[2:4] [100,200,300]
Old : 2019-04-18 05:52:30.546  [1,2,3]
New : 2019-04-18 05:53:08.617  [100,200,300]
mrk> pvget DBRdoubleArray
DBRdoubleArray 2019-04-18 05:53:08.617  [100,200,300]

Appendix I: Description of the PVStructure created by createRequest

NOTE: This section is only of interest to someone who is implementing code that has to introspect a structure generated by createRequest.

pvRequest is a PVStructure that describes 1) record options and 2) field requests and options. It has the following structure:

structure
  structure record
    structure _options
      option
      ...
  structure field
    structure fieldName
      structure _options
        option
        ...
      structure fieldName
        structure _options
          option
          ...
        ...  
  structure putField
    structure fieldName
      structure _options
        option
        ...
      structure fieldName
        structure _options
          option
          ...
        ...  
  structure getField
    structure fieldName
      structure _options
        option
        ...
      structure fieldName
        structure _options
          option
          ...
        ...  

where

record
The options that apply to the entire record.
option
This is of the form string <name> <value>
field
Definitions that select fields of the PVRecord and options for the fields. This definition is recursive.
fieldName
The field name that will appear in the PVStructure that is a copy of the fields selected from the PVRecord.

Note:

An example of option is process.

Process is a record option:

structure
  structure record
    structure _options
      string process true
   

For example if process is an option to createGet then the record will be processed before data is fetched. NOTE: scalarType boolean is also supported.

The following is an example of a field option:

structure 
    structure field
        structure value
            structure _options
                string monitorAlgorithm onChange

Some examples are:

request 
structure 

request alarm,timeStamp,power.value
structure 
    structure alarm
    structure timeStamp
    structure power
        structure value

request record[process=true]field(alarm,timeStamp,power.value)
structure 
    structure record
        structure _options
            string process true
    structure field
        structure alarm
        structure timeStamp
        structure power
            structure value

request record[process=true]field(alarm,timeStamp[algorithm=onChange,causeMonitor=false],power{value,alarm})
structure 
    structure record
        structure _options
            string process true
    structure field
        structure alarm
        structure timeStamp
            structure _options
                string algorithm onChange
                string causeMonitor false
        structure power
            structure value
            structure alarm

request record[process=true,xxx=yyy]field(alarm,timeStamp[causeMonitor=true],power.value)
structure 
    structure record
        structure _options
            string process true
            string xxx yyy
    structure field
        structure alarm
        structure timeStamp
            structure _options
                string causeMonitor true
        structure power
            structure value

Appendix II: BNF syntax for request

NOT DONE

<request> :=