pvaClient JSON support

2019.08.09

Editors:
Marty Kraimer

NOTES:

This product is available via an open source license

Table of Contents

Overview

Background

This document provides a description of the JSON support provided by pvaClientCPP. The put support is modeled on the JSON syntax for pvput. The get support is modeled on the JSON syntax for pvget

The following are some examples:

mrk> pvput PVRdouble '{"value":"10"}'
Old : <undefined>              0 
New : 2019-07-18 13:23:52.973  10 
mrk> pvput PVRdouble value=10.5
Old : 2019-07-22 10:02:40.140 10 
New : 2019-07-22 14:20:47.197  20 10.5 
mrk> pvput PVRdoubleArray '{"value":["10","20"]}'
Old : <undefined>              []
New : 2019-07-18 13:24:01.187  [10,20]
mrk> pvput -r "value,alarm"  PVRdouble '{"alarm":{"severity":"1","status":"2","message":"I did this"}}'
Old : 10 
New : 10 MINOR DRIVER I did this
mrk> pvget -M json -r "value,alarm" PVRlong
PVRlong {"value": 0,"alarm": {"severity": 0,"status": 0,"message": ""}}

pvput also provides extra support for a top level field named value that is a scalar or scalarArray. For example:

mrk> pvput PVRdouble 8
Old : 2019-07-18 13:23:52.973  10 
New : 2019-08-04 06:24:41.856  8 
mrk> pvput PVRdoubleArray [100,200,300]
Old :  2019-07-18 13:24:01.187  [10,20]
New : 2019-08-04 06:25:23.686  [100,200,300]

JSON syntax brief description

The following is a brief description of the JSON syntax for pvData. See json for a more complete description of the JSON syntax.

A PVStructure is represented by:

{...} 
where ... is a comma separated set of name,value pairs; One pair for each subfield in the PVStructure.

The name,value pair for each each PVData field is:

"name":<value>
where name is the PVField name. and <value> is :
"value"    // field must be scalar. value is the field value.
or
value      // field must be numeric scalar
or
true       // field must be boolean
or
false      // field must be boolean
or
[<value>,<value>...]  // field must be a scalarArray 
or
{...}     // field must be a structure

The JSON support implemented by pvaClientCPP uses similar syntax. This is what is described in this document.

Theory Of Operation

epics-base json

Starting with the 3.15 releases of epics base, a JSON parser is provided.

For this document it is not necessary to describe the parser other than to say that it is required because it is used by pvDataCPP.

pvDataCPP json

pvDataCPP provides JSON support for pvData objects. The user interface is described in json.h. One of the methods is:

void parseJSON(std::istream& strm,
               PVField& dest,
               BitSet *assigned=0);

For a PVField it provides the following:

scalar
All the scalar types are supported.
boolean
value can be true or false
integer
signed and unsigned integers of length 8, 16, 32, and 64 bits are supported.
float
IEEE 32 bit floating point.
double
IEEE 64 bit floating point.
string
std::string
scalarArray
Supported for all scalar types.
Note: new values are appended to existing values.
structure
Support for all fields that have type scalar, scalarArray, or structure.
union
No support.
structureArray
No support.
unionArray
No support.

Another method is:

void printJSON(std::ostream& strm,
               const PVStructure& val,
               const BitSet& mask,
               const JSONPrintOptions& opts = JSONPrintOptions());

pvaClientCPP uses both of the above methods.

pvaClientCPP json

One of the classes implemented by pvaClientCPP is PvaClientData, which has the following new methods:

void parse(const std::vector<std::string> &args);
void streamJSON(
               std::ostream& strm,
               bool ignoreUnprintable = true,
               bool multiLine = false);
void zeroArrayLength();
where
parse
This is used to change fields in the pvStructure in PvaClientData via JSON syntax.
streamJSON
This produces JSON syntax for the data in pvStructure.
zeroArrayLength

This method recursively locates all array fields in the PVStructure attached to PvaClientData and sets the array length to 0.

parseJSON shown above appends scalarArray values to the current value. This is usually not what the client wants. A common way that a client uses parse is to first get the current value of the PVStructure attached to PvaClientData and then to set new values. zeroArrayLength can be called before calling parse if the client wants to set new values to array fields.

This should NOT be called if parse is being called with the special enumerated syntax shown below, because it will set choices to an empty array.

Since PvaClientData is the base data class for channelGet, channelPut, channelPutGet, and channelMonitor, it is available for use by each. The examples shown below use it for channelPut and channelPutGet.

Syntax for parse

NOTES:

Each argument provides data for fields in the current PVStructure. The syntax for each argument is one of the following:

json
or
field=json
or
field=value           // enumerated
or
field=type=json // restricted union
json
This means pure json syntax as defined by pvDataCPP. An example is:
parsePut PVRdouble '{"value":"10"}'
field=json
field must be name.name... It selects a subfield of the PVStructure. If the subfield does not exist an exception is thrown. The json syntax is for the selected subfield. An example is:
parsePut PVRBigRecord scalar.string.value='"this is a string"'
field=value
This is a special case for an enumerated structure. The value specifies a valid choice. The index is what changes. An example is:
 parsePut -z false PVRenum value=one
field=type=json
field must select a restricted union field. type selects the restriced union type. An instance is created and the json syntax writes to the new instance.
Note that if type again selects a restricted union recursion is supported. For example field=type=type=json Examples are:
parsePut PVRrestrictedUnion value='string="test string"'
parsePut PVRrestrictedUnion value='union_t=point={"x":".1","y":".2"}'

exampleCPP/json

This contains examples that use the JSON methods provided with PvaClientData.

command line tools

parsePut

This is a command that allows a client to perform a channelPut, with JSON format arguments.
The command provides:

parsePut -help
 -h -p provider -r request  -z zeroarray - d debug channelName args 

The source contains the following code:

PvaClientPtr pva= PvaClient::get(provider);
PvaClientChannelPtr channel = pva->channel(channelName,provider,2.0);
PvaClientPutPtr put = channel->put(request);
PvaClientPutDataPtr putData(put->getData());
if(zeroarray) putData->zeroArrayLength();
putData->getChangedBitSet()->clear();
putData->parse(args);
put->put();

Examples are shown below.

parsePutGet

This is similar to parsePut except that it uses channelPutGet.

jsonGet

This is a command that display data obtained via channelGet with JSON syntax.
The command provides:

jsonGet -help
 -h -p provider -r request -m multiline - d debug channelName 

An example is:

jsonGet -r "value,alarm" PVRstring
_____jsonGet channel PVRstring provider pva request value,alarm multiline false debug false
{"value": "three","alarm": {"severity": 0,"status": 0,"message": ""}}

The source contains the following code:

PvaClientPtr pva(PvaClient::get(provider));
PvaClientGetDataPtr pvData =
    pva->channel(channelName,provider,2.0)->get(request)->getData();
std::ostringstream os;
pvData->streamJSON(os,true,multiline);
cout << os.str() << "\n";

client scripts

json/client/scripts has examples that use the command line tools.

Running the examples

Start the IOC for the examples

In a window start the IOC:

mrk> pwd
/home/epicsv4/masterCPP/exampleCPP/database/iocBoot/exampleDatabase
mrk> ../../bin/linux-x86_64/exampleDatabase st.cmd 
...
epics> 

Some Suggestions

Set Path

For example I have added the following to my path.

export PATH=$PATH:/home/epicsv4/masterCPP/exampleCPP/json/bin/${EPICS_HOST_ARCH}

Other tools

Don't forget about pvlist, pvinfo, and pvget. For example:

mrk> pvinfo PVRrestrictedUnion
PVRrestrictedUnion
Server: 10.0.0.48:5075
Type:
    epics:nt/NTUnion:1.0
        union value
            string string
            string[] stringArray
            structure point
                double x
                double y
            union union_t
                string string
                string[] stringArray
                structure point
                    double x
                    double y
        time_t timeStamp
            long secondsPastEpoch
            int nanoseconds
            int userTag
mrk> pvget -r ""  PVRrestrictedUnion
PVRrestrictedUnion epics:nt/NTUnion:1.0 
    union value
        (none)
    time_t timeStamp <undefined>              
        long secondsPastEpoch 0
        int nanoseconds 0
        int userTag 0
mrk> parsePut PVRrestrictedUnion value='string="test string"'
_____parsePut channel PVRrestrictedUnion provider pva request  zeroarray true debug false
mrk> pvget -r ""  PVRrestrictedUnion
PVRrestrictedUnion epics:nt/NTUnion:1.0 
    union value
        string  test string
    time_t timeStamp 2019-08-03 07:00:12.608  
        long secondsPastEpoch 1564830012
        int nanoseconds 607894337
        int userTag 0
mrk> 
mrk> pvlist
GUID 0x2569455D000000007930A800 version 2: tcp@[10.0.0.48:5075, 192.168.124.1:5075]
mrk> pvlist 0x2569455D000000007930A800
DBRint00
DBRint01
DBRstring00
...
DBRdouble
DBRdouble00
...
DBRao01
DBRenum01
DBRmbbo00
DBRmbbo01
DBRcounter01
DBRcalc00
DBRmbbiwierd
DBRstringArray01
DBRdoubleArray
DBRbyteArray01
DBRshortArray01
DBRintArray01
DBRfloatArray01
DBRdoubleArray01
DBRbo00
DBRbo01
PVRBigRecord
PVRboolean
PVRbooleanArray
PVRbyte
PVRbyteArray
PVRdouble
PVRdouble01
PVRdouble01Array
PVRdouble02
PVRdouble02Array
PVRdouble03
PVRdouble03Array
PVRdouble04
PVRdouble04Array
PVRdouble05
PVRdouble05Array
PVRdoubleArray
PVRdumbPowerSupply
PVRenum
PVRfloat
PVRfloatArray
PVRhelloPutGet
PVRhelloRPC
PVRint
PVRintArray
PVRlong
PVRlongArray
PVRremoveRecord
PVRrestrictedUnion
PVRrestrictedUnionArray
PVRshort
PVRshortArray
PVRsoft
PVRstring
PVRstringArray
PVRstructureArray
PVRtraceRecord
PVRubyte
PVRubyteArray
PVRuint
PVRuintArray
PVRulong
PVRulongArray
PVRushort
PVRushortArray
PVRvariantUnion
PVRvariantUnionArray

Also monitoring changes is usefull for seeing the result of running the examples. For example in one window run:

mrk> pvget -m -r "" PVRrestrictedUnion
PVRrestrictedUnion epics:nt/NTUnion:1.0 
    union value
        (none)
    time_t timeStamp <undefined>              
        long secondsPastEpoch 0
        int nanoseconds 0
        int userTag 0

Then in another window run:

mrk> pwd
/home/epicsv4/masterCPP/exampleCPP/json/client/scripts
mrk> ./exampleUnion

In the monitor window you will see:

PVRrestrictedUnion epics:nt/NTUnion:1.0 
    union value
        string  test string
    time_t timeStamp 2019-08-03 10:53:40.260  
        long secondsPastEpoch 1564844020
        int nanoseconds 260336547
PVRrestrictedUnion epics:nt/NTUnion:1.0 
    union value
        string[]  ["test string", "two"]
    time_t timeStamp 2019-08-03 10:53:40.804  
        int nanoseconds 803675341
PVRrestrictedUnion epics:nt/NTUnion:1.0 
    union value
        structure 
            double x 0.1
            double y 0.2
    time_t timeStamp 2019-08-03 10:53:41.324  
        long secondsPastEpoch 1564844021
        int nanoseconds 324152690
PVRrestrictedUnion epics:nt/NTUnion:1.0 
    union value
        union 
            string  test
    time_t timeStamp 2019-08-03 10:53:41.843  
        int nanoseconds 843287469
PVRrestrictedUnion epics:nt/NTUnion:1.0 
    union value
        union 
            structure 
                double x 0.1
                double y 0.2
    time_t timeStamp 2019-08-03 10:53:42.361  
        long secondsPastEpoch 1564844022
        int nanoseconds 360729910

exampleCPP/json/client/scripts/exampleScalar

This example is for records like the following:

mrk> pvinfo PVRdouble
PVRdouble
Server: 10.0.0.48:5075
Type:
    epics:nt/NTScalar:1.0
        double value
        alarm_t alarm
            int severity
            int status
            string message
        time_t timeStamp
            long secondsPastEpoch
            int nanoseconds
            int userTag
For each record value ls a scalar.

exampleScalar is

#!/bin/sh
source ./setEnv
${PARSE}/parsePut -r "value" PVRdouble value=1
sleep .5
${PARSE}/parsePut -r "value" PVRdouble '{"value":2}'
sleep .5
${PARSE}/parsePutGet -r "putField(value)getField(value)" PVRdouble value=10
sleep .5
${PARSE}/parsePut -r "value" PVRstring value='"one"'
sleep .5
${PARSE}/parsePut -r "value" PVRstring '{"value":"two"}'
sleep .5
${PARSE}/parsePutGet -r "putField(value)getField(value)" PVRstring value='"three"'
sleep .5
${PARSE}/parsePut  -r "value" PVRboolean value=true
sleep .5
${PARSE}/parsePut  -r "value" PVRboolean '{"value":false}'
sleep .5
${PARSE}/parsePutGet -r "putField(value)getField(value)" PVRboolean value=true
sleep .5
${PARSE}/parsePut -r "value" DBRdouble value=1
sleep .5
${PARSE}/parsePut -r "value" -p ca DBRdouble value=5
sleep .5
${PARSE}/parsePut -r "value" DBRstring00 value='"one"'
sleep .5
${PARSE}/parsePut -r "value" -p ca DBRstring00 value='"two"'

Running it produces:

./exampleScalar
_____parsePut channel PVRdouble provider pva request value zeroarray true debug false
_____parsePut channel PVRdouble provider pva request value zeroarray true debug false
_____parsePutGet channel PVRdouble provider pva request putField(value)getField(value) zeroarray true debug false
structure 
    double value 10

_____parsePut channel PVRstring provider pva request value zeroarray true debug false
_____parsePut channel PVRstring provider pva request value zeroarray true debug false
_____parsePutGet channel PVRstring provider pva request putField(value)getField(value) zeroarray true debug false
structure 
    string value three

_____parsePut channel PVRboolean provider pva request value zeroarray true debug false
_____parsePut channel PVRboolean provider pva request value zeroarray true debug false
_____parsePutGet channel PVRboolean provider pva request putField(value)getField(value) zeroarray true debug false
structure 
    boolean value true

_____parsePut channel  DBRdouble provider pva request value zeroarray true debug false
_____parsePut channel  DBRdouble provider ca request value zeroarray true debug false
_____parsePut channel  DBRstring00 provider pva request value zeroarray true debug false
_____parsePut channel  DBRstring00 provider ca request value zeroarray true debug false

exampleCPP/json/client/scripts/exampleScalarArray

This example is for records like the following:

mrk> pvinfo PVRdoubleArray
PVRdoubleArray
Server: 10.0.0.48:5075
Type:
    epics:nt/NTScalarArray:1.0
        double[] value
        alarm_t alarm
            int severity
            int status
            string message
        time_t timeStamp
            long secondsPastEpoch
            int nanoseconds
            int userTag
For each record value ls a scalarArray.

exampleScalarArray is:

source ./setEnv
${PARSE}/parsePut -r "" PVRdoubleArray value='[1,2,3]'
sleep .5
${PARSE}/parsePut -r "" DBRdoubleArray value='[1,2,3]'
sleep .5
${PARSE}/parsePut -r "" PVRdoubleArray '{"value":[10,20,30]}'
sleep .5
${PARSE}/parsePutGet -r "putField(value)getField(value)" PVRdoubleArray value='[100,200,300]'
sleep .5
${PARSE}/parsePut -r "" PVRstringArray value='["one","two","three"]'
sleep .5
${PARSE}/parsePut -r "" DBRstringArray01 value='["one","two","three"]'
sleep .5
${PARSE}/parsePut -r "" PVRstringArray '{"value":["one again","two again","three again"]}'
sleep .5
${PARSE}/parsePutGet -r "putField(value)getField(value)" PVRstringArray value='["once more","two again","three again"]'
sleep .5
${PARSE}/parsePut  -r "" PVRbooleanArray value='[true,false,true]'
sleep .5
${PARSE}/parsePut  -r "" PVRbooleanArray '{"value":[false,true,false]}'
sleep .5
${PARSE}/parsePutGet -r "putField(value)getField(value)" PVRbooleanArray value='[true,false,true]'

exampleCPP/json/client/scripts/exampleEnum

This example is for records like the following:

mrk> pvinfo PVRenum
PVRenum
Server: 10.0.0.48:5075
Type:
    epics:nt/NTEnum:1.0
        enum_t value
            int index
            string[] choices
        alarm_t alarm
            int severity
            int status
            string message
        time_t timeStamp
            long secondsPastEpoch
            int nanoseconds
            int userTag

exampleEnum is:

#!/bin/sh
source ./setEnv
${PARSE}/parsePut -z false PVRenum value=one
sleep .5
${PARSE}/parsePut -z false DBRenum01 value=one
sleep .5
${PARSE}/parsePutGet -z false -r "putField(value)getField(value)" PVRenum value=zero
sleep .5
${PARSE}/parsePut -z false -p ca DBRenum01 value=zero
sleep .5
${PARSE}/parsePut PVRenum '{"value":{"index":1}}'

exampleCPP/json/client/scripts/exampleUnion

This example is for the following record:

mrk> pvinfo PVRrestrictedUnion
PVRrestrictedUnion
Server: 10.0.0.48:5075
Type:
    epics:nt/NTUnion:1.0
        union value
            string string
            string[] stringArray
            structure point
                double x
                double y
            union union_t
                string string
                string[] stringArray
                structure point
                    double x
                    double y
        time_t timeStamp
            long secondsPastEpoch
            int nanoseconds
            int userTag

exampleUnion is:

#!/bin/sh
source ./setEnv
${PARSE}/parsePut PVRrestrictedUnion value='string="test string"'
sleep .5
${PARSE}/parsePut PVRrestrictedUnion value='stringArray=["test string","two"]'
sleep .5
${PARSE}/parsePut PVRrestrictedUnion value='point={"x":".1","y":".2"}'
sleep .5
${PARSE}/parsePut PVRrestrictedUnion value='union_t=string="test"'
sleep .5
${PARSE}/parsePut PVRrestrictedUnion value='union_t=point={"x":".1","y":".2"}'

exampleCPP/json/client/scripts/exampleBigRecord

This example is for the following record:

mrk> pvinfo PVRBigRecord
PVRBigRecord
Server: 10.0.0.48:5075
Type:
    structure
        time_t timeStamp
            long secondsPastEpoch
            int nanoseconds
            int userTag
        structure scalar
            structure boolean
                boolean value
            structure byte
                byte value
            structure long
                long value
            structure double
                double value
            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
            structure
                string name
                string value
        union restrictedUnion
            string string
            string[] stringArray
        any variantUnion

exampleBigRecord is:

#!/bin/sh
source ./setEnv
${PARSE}/parsePut 
sleep .5
${PARSE}/parsePut -r "" PVRBigRecord scalar.double.value=10 scalarArray.double.value='[100,200]'
sleep .5
${PARSE}/parsePut -r "" PVRBigRecord '{"scalar":{"double":{"value":20}}}' '{"scalarArray":{"double":{"value":[1000,2000]}}}'
sleep .5
${PARSE}/parsePut -r "" PVRBigRecord scalar.double.value=10 scalar.string.value='" this is a string"'
sleep .5
${PARSE}/parsePut -r "" PVRBigRecord '{"scalar":{"double":{"value":20},"string":{"value":"this is also a string"}}}'
sleep .5
${PARSE}/parsePutGet -r "putField(),getField(scalar.double,scalarArray.double)" PVRBigRecord scalar.double.value=10 scalarArray.double.value='[100,200]'