dbGroup Structures

Table of Contents


Introduction

Given a set of PVs there are a number of ways of packaging their values for group operations. Basic choices are between

  1. Packaging PVs using structures of named fields or using arrays
  2. Encoding just the value field or other fields such as timestamps or alarms
  3. Grouping values with values and timestamps with timestamps and so on or keeping each value with its timestamp
  4. Providing additional timestamps alarms etc. for the group
  5. Heterogeneous arrays (via unions) versus homogeneous arrays

This document lists the various options, providing a name for each, and discusses the use cases and pros and cons of each structure.

Possible Structures

Lets assume we have PVs D, {Di} with double values and E, {Ei} with enum_t values, all with timestamps and alarms, whose structures are respectively

structure
    double value
    alarm_t alarm 
    time_t timeStamp

and

structure
    enum_t value
    alarm_t alarm 
    time_t timeStamp

(In what follows the extension to more than two PVs, to other scalars and to PVs with additional fields, such as control limits, should be obvious.)

The following structures are possible:

Composite (values only)

The first possibility is to encode the value fields of the PVs only in a structure the names of whose fields are the names of the PVs.

E.g. for a double PV D and an enum PV E the structure would be:

structure
    double D
    enum_t E

I've called storing PV values in a structure in this way in general, whether it encodes just the value fields or not, "composite", and in this case I refer to it as "composite (values only)".

Advantages:

  1. Small, simple structure
  2. Easy to get any value by name
  3. Good if collection of PVs will not change
  4. Supports efficient monitoring
  5. Good for (complex) control and status, providing additional fields not needed

Disadvantages:

  1. Doesn't encode other fields from the PVs in the group (timeStamp, alarm etc.)
  2. For an arbitrary set of PVs, it is difficult to add additional fields, like an overall alarm or timeStamp or a user extension, without confusion.
  3. Not so useful if you want your values in array (e.g. piping into other application)
  4. Unsuitable if collection of PVs will change

Composite (full structure)

Another possibility is to encode the all the fields of the PVs in a structure the names of whose fields are the names of the PVs. E.g. for such a composite of a double PV D and an enum PV E the structure would be:

structure
    structure D
        double value
        alarm_t alarm 
        time_t timeStamp
    structure E
        enum_t value
        alarm_t alarm 
        time_t timeStamp

I've called such a structure "composite (full structure)".

Advantages:

  1. Can get any field of any PV in group
  2. Easy to get value of any field of any PV by name
  3. Good if collection of PVs will not change
  4. Supports efficient monitoring
  5. Good for full (complex) control and status

Disadvantages:

  1. More complicated structure (but can get subset of it using pvRequest)
  2. Difficult to add additional fields without confusion (like an overall alarm or timeStamp or a user extension)
  3. Not so useful if you want your values in array (e.g. piping values into other application)
  4. Unsuitable if collection of PVs will change

Group (values only)

The PVs can be encoded one level down in a structure subfield (value), allowing the collection of PVs to have their own timestamp, alarm, description etc. without confusion. I've called this arrangement "group". So to collect the values of D and E together in a group the structure would be:

structure
    structure value
        double D
        enum_t E
    alarm_t alarm      :opt
    time_t timeStamp   :opt

Advantages:

  1. Relatively small, simple structure
  2. Easy to get any value by name
  3. Supports additional fields without confusion (like an overall alarm or timeStamp or a user extension)
  4. Good if collection of PVs will not change
  5. Supports efficient monitoring
  6. Good for (complex) control and status, providing additional fields not needed

Disadvantages:

  1. Slightly more complex than composite
  2. Doesn't encode other fields from the PVs in the group (timeStamp, alarm etc.)
  3. Not so useful if you want your values in array (e.g. piping into other application)
  4. Unsuitable if collection of PVs will change

Group (full structure)

The group arrangement could also be used with the full structure, as per composite

structure
    structure value
        structure D
            double value
            alarm_t alarm 
            time_t timeStamp
        structure E
            enum_t value
            alarm_t alarm 
            time_t timeStamp
    string descriptor  :opt
    alarm_t alarm      :opt
    time_t timeStamp   :opt

Advantages:

  1. Can get any field of any PV in group by name
  2. Easy to get value of any field of any PV
  3. Supports additional fields without confusion (like an overall alarm or timeStamp or a user extension)
  4. Good if collection of PVs will not change
  5. Supports efficient monitoring
  6. Good for full control and status

Disadvantages:

  1. More complicated structure than both composite (full structure) and group (values only) (but can get subset of it using pvRequest)
  2. Not so useful if you want your values in array (e.g. piping values into other application)
  3. Unsuitable if collection of PVs will change

Composite or Group within Union

The above 4 structures can be place inside a union, e.g. if the group of PVs is likely to change.

Multichannel

An alternative to arranging things as structures is to use arrays. For example we can use NTMultiChannel

structure
    anyunion_t[]  value             
    string[]      channelName        
    string        descriptor         :opt
    alarm_t       alarm              :opt
    time_t        timeStamp          :opt
    int[]         severity           :opt
    int[]         status             :opt
    string[]      message            :opt
    long[]        secondsPastEpoch   :opt
    int[]         nanoseconds        :opt
    int[]         userTag            :opt

Here anyunion_t[] can be a regular union array union[] for some appropriate choice of regular union or a variant union array("any[]").

The simplest case would be just to store the values, along with the channel names. So for D and E encoded together in a typical value, assuming using a variant union, might look like this

epics:nt/NTMultiChannel:1.0 
    any[] value
        any 
            double  2.71
        any 
            enum_t 
                int index 0
                string[] choices [Off,On]
    string[] channelName [D,E]

The type can also encode time stamps, alarms for the group as well as for the individual PVs:

epics:nt/NTMultiChannel:1.0 
    any[] value
        any 
            double  2.71
        any 
            enum_t 
                int index 0
                string[] choices [Off,On]
    string[] channelName [D,E]
    string descriptor 
    alarm_t alarm
        int severity 0
        int status 0
        string message 
    time_t timeStamp
        long secondsPastEpoch 1437393283
        int nanoseconds 60995462
        int userTag 0
    int[] severity [1,2]
    int[] status [3,3]
    string[] message [HIGH_ALARM,STATE_ALARM]
    long[] secondsPastEpoch [1437393283,1437393283]
    int[] nanoseconds [60766804,60768106]
    int[] userTag [0,0]

Advantages:

  1. Easy to get an array of values, although heterogeneous, (e.g. for piping into service)
  2. Supports additional fields without confusion (like an overall alarm or timeStamp or a user extension)
  3. Can encode timestamps, alarms of each PV
  4. Suitable if collection of PVs will change
  5. May be a good choice for returning from a service

Disadvantages:

  1. Harder to get (fields of) individual PVs by name
  2. Harder to match values, time stamps or alarms associated with an individual PV
  3. Less useful if PVs have different fields (e.g. some have alarms and some don't)
  4. Does not support efficient monitoring
  5. Less useful for full (complex) control and status

Multichannel (full structure in value field)

It would also be possible to store the structures in NTMultiChannel. (It's not how the type was intended to be used, but for completeness ...). The structure would be

structure
    anyunion_t[]  value              
    string[]      channelName        
    string        descriptor         :opt
    alarm_t       alarm              :opt
    time_t        timeStamp          :opt
and a typical value might be
epics:nt/NTMultiChannel:1.0 
    any[] value
        any 
            epics:nt/NTScalar:1.0 
                double value 2.71
                alarm_t alarm
                    int severity 1
                    int status 3
                    string message HIGH_ALARM
                time_t timeStamp
                    long secondsPastEpoch 1437393283
                    int nanoseconds 60766804
                    int userTag 0
        any 
            epics:nt/NTEnum:1.0 
                enum_t value
                    int index 0
                    string[] choices [Off,On]
                alarm_t alarm
                    int severity 2
                    int status 3
                    string message STATE_ALARM
                time_t timeStamp
                    long secondsPastEpoch 1437393283
                    int nanoseconds 60768106
                    int userTag 0
    string[] channelName [D,E]
    string descriptor 
    alarm_t alarm
        int severity 0
        int status 0
        string message 
    time_t timeStamp
        long secondsPastEpoch 1437393283
        int nanoseconds 61030887
        int userTag 0

Advantages:

  1. Supports additional fields without confusion (like an overall alarm or timeStamp or a user extension)
  2. Can encode timestamps, alarms of each PV
  3. Suitable if collection of PVs will change
  4. Suitable if if PVs have different fields (e.g. some have alarms and some don't)
  5. Easier to match values, time stamps or alarms associated with an individual PV than the normal use of multichannel

Disadvantages:

  1. Less suitable for getting array of values e.g. piping into service
  2. Harder to get (fields of) individual PVs by name
  3. Does not support efficient monitoring
  4. Less useful for full (complex) control and status

ScalarMultichannel

To package together multiple scalar values we could use an NTScalarMultiChannel:

structure
    anyunion_t[]  value             
    string[]      channelName        
    string        descriptor         :opt
    alarm_t       alarm              :opt
    time_t        timeStamp          :opt
    int[]         severity           :opt
    int[]         status             :opt
    string[]      message            :opt
    long[]        secondsPastEpoch   :opt
    int[]         nanoseconds        :opt
    int[]         userTag            :opt

The simplest case would be just to store the values, along with the channel names. For double PVs D1 and for D2 a typical value might be

epics:nt/NTScalarMultiChannel:1.0 
    double[] value [2.71,3.14]
    string[] channelName [D1,D2]

The type can also encode time stamps, alarms for the group as well as for the individual PVs:

epics:nt/NTScalarMultiChannel:1.0 
    double[] value [2.71,3.14]
    string[] channelName [D1,D2]
    string descriptor 
    alarm_t alarm
        int severity 0
        int status 0
        string message 
    time_t timeStamp
        long secondsPastEpoch 1437393283
        int nanoseconds 61103269
        int userTag 0
    int[] severity [1,2]
    int[] status [3,3]
    string[] message [HIGH_ALARM,HIHI_ALARM]
    long[] secondsPastEpoch [1437393283,1437393283]
    int[] nanoseconds [60766804,60754272]
    int[] userTag [0,0]

Advantages:

  1. Easy to get an array of values (e.g. for piping into service)
  2. Array is homogeneous
  3. Supports additional fields without confusion (like an overall alarm or timeStamp or a user extension)
  4. Can encode timestamps, alarms of each PV
  5. Suitable if collection of PVs will change
  6. May be a good choice for returning from a service

Disadvantages:

  1. Not suitable for PVs of different types or non scalars
  2. Harder to get (fields of) individual PVs by name
  3. Harder to match values, time stamps or alarms associated with an individual PV
  4. Less useful if PVs have different fields (e.g. some have alarms and some don't)
  5. Does not support efficient monitoring
  6. Less useful for full (complex) control and status

Custom structure

A final possibility is that fields of a collection of PVs can be grouped into an arbitrary structure.

Examples might include motor records or similar complex control PVs or hierarchical structures of PVs. (I understand some people like the idea of building a hierarchical structure server-side and others will hate the suggestion. This list is intended to be comprehensive.)

Conclusion

There are number of ways of packaging multiple PVs into a single structure. We wouldn't necessarily want to support all of these but there may be value in supporting more than one, such as a scalar array for getting a collection of PV values or a group or composite for complex control or efficient monitoring.

This suggests careful separation of the presentation of a collection of a group of PVs in a structure from the management of that group of PVs.

Appendix: Example

Here is the output from getting examples of PVs employing the above structures:

[~]$ pvget -r "field()" D E CompositeValue CompositeStructure GroupValue GroupFull
MultiValue MultiAll MultiStruct ScalarMultiValue ScalarMultiAll 

D
epics:nt/NTScalar:1.0 
    double value 2.71
    alarm_t alarm MINOR RECORD HIGH_ALARM
    time_t timeStamp 2015-07-20T13:00:23.124 0


E
epics:nt/NTEnum:1.0 
    enum_t value Off
    alarm_t alarm MAJOR RECORD STATE_ALARM
    time_t timeStamp 2015-07-20T13:00:23.124 0


CompositeValue
Composite_ValueStructure 
    double D 2.71
    enum_t E Off


CompositeStructure
Composite_FullStructure 
    epics:nt/NTScalar:1.0 D
        double value 2.71
        alarm_t alarm MINOR RECORD HIGH_ALARM
        time_t timeStamp 2015-07-20T13:00:23.124 0
    epics:nt/NTEnum:1.0 E
        enum_t value Off
        alarm_t alarm MAJOR RECORD STATE_ALARM
        time_t timeStamp 2015-07-20T13:00:23.124 0


GroupValue
Group_ValueStructure 
    structure value
        double D 2.71
        enum_t E Off
    time_t timeStamp 2015-07-20T13:00:23.124 0


GroupFull
Group_ValueStructure 
    Group_FullStructure value
        epics:nt/NTScalar:1.0 D
            double value 2.71
            alarm_t alarm MINOR RECORD HIGH_ALARM
            time_t timeStamp 2015-07-20T13:00:23.124 0
        epics:nt/NTEnum:1.0 E
            enum_t value Off
            alarm_t alarm MAJOR RECORD STATE_ALARM
            time_t timeStamp 2015-07-20T13:00:23.124 0
    time_t timeStamp 2015-07-20T13:00:23.124 0


MultiValue
epics:nt/NTMultiChannel:1.0 
    any[] value
        any 
            double  2.71
        any 
            enum_t  Off
    string[] channelName [D,E]
    string descriptor 
    alarm_t alarm NO_ALARM NO_STATUS <no message>
    time_t timeStamp 2015-07-20T13:00:23.124 0


MultiAll
epics:nt/NTMultiChannel:1.0 
    any[] value
        any 
            double  2.71
        any 
            enum_t  Off
    string[] channelName [D,E]
    string descriptor 
    alarm_t alarm NO_ALARM NO_STATUS <no message>
    time_t timeStamp 2015-07-20T13:00:23.124 0
    int[] severity [1,2]
    int[] status [3,3]
    string[] message [HIGH_ALARM,STATE_ALARM]
    long[] secondsPastEpoch [1437393623,1437393623]
    int[] nanoseconds [123912002,123913343]
    int[] userTag [0,0]


MultiStruct
epics:nt/NTMultiChannel:1.0 
    any[] value
        any 
            epics:nt/NTScalar:1.0 
                double value 2.71
                alarm_t alarm MINOR RECORD HIGH_ALARM
                time_t timeStamp 2015-07-20T13:00:23.124 0
        any 
            epics:nt/NTEnum:1.0 
                enum_t value Off
                alarm_t alarm MAJOR RECORD STATE_ALARM
                time_t timeStamp 2015-07-20T13:00:23.124 0
    string[] channelName [D,E]
    string descriptor 
    alarm_t alarm NO_ALARM NO_STATUS <no message>
    time_t timeStamp 2015-07-20T13:00:23.124 0


ScalarMultiValue
epics:nt/NTScalarMultiChannel:1.0 
    double[] value [2.71,3.14]
    string[] channelName [D1,D2]
    string descriptor 
    alarm_t alarm NO_ALARM NO_STATUS <no message>
    time_t timeStamp 2015-07-20T13:00:23.124 0


ScalarMultiAll
epics:nt/NTScalarMultiChannel:1.0 
    double[] value [2.71,3.14]
    string[] channelName [D1,D2]
    string descriptor 
    alarm_t alarm NO_ALARM NO_STATUS <no message>
    time_t timeStamp 2015-07-20T13:00:23.124 0
    int[] severity [1,2]
    int[] status [3,3]
    string[] message [HIGH_ALARM,HIHI_ALARM]
    long[] secondsPastEpoch [1437393623,1437393623]
    int[] nanoseconds [123912002,123895073]
    int[] userTag [0,0]