diff --git a/.github/workflows/ci-scripts-build.yml b/.github/workflows/ci-scripts-build.yml index 8a7f81c..b85339f 100644 --- a/.github/workflows/ci-scripts-build.yml +++ b/.github/workflows/ci-scripts-build.yml @@ -156,7 +156,7 @@ jobs: - name: Generate Docs if: matrix.doc run: | - sudo apt-get -y install doxygen python-is-python3 python3-breathe + sudo apt-get -y install doxygen python-is-python3 python3-breathe inkscape make -C documentation - name: otool if: ${{ always() && runner.os == 'macOS' }} diff --git a/documentation/.gitignore b/documentation/.gitignore index ecb4559..2c7ad6a 100644 --- a/documentation/.gitignore +++ b/documentation/.gitignore @@ -1,5 +1,7 @@ pvxs.tag /xml/ /html/ -/_build +/_build/ +/_image/ *.tmp +*.inv diff --git a/documentation/Makefile b/documentation/Makefile index cb44b2f..ebf8d27 100644 --- a/documentation/Makefile +++ b/documentation/Makefile @@ -1,4 +1,6 @@ PYTHON?=python3 +CURL?=curl +INKSCAPE?=inkscape # documentation generation requires Doxygen and Sphinx with the breathe extension # @@ -7,8 +9,8 @@ PYTHON?=python3 all: gen clean: - rm -rf xml pvxs.tag - rm -rf html _build + rm -rf xml default pvxs.tag + rm -rf html _build _image # conf.py reads CONFIG_PVXS_VERSION to extract version number gen: ../include/pvxs/version.h @@ -16,7 +18,16 @@ gen: ../include/pvxs/version.h $(PYTHON) -m sphinx -j auto -b html . html cp qsrv2-schema-0.json html/ +gen: _image/nt_table1.svg +gen: _image/nt_table2.svg +gen: _image/nt_table3.svg +gen: _image/nt_table4.svg + commit: gen ./commit-gh.sh documentation/html/ html/.nojekyll html/*.* html/_*/*.* -.PHONY: all clean gen commit +_image/%.svg: %.svg + install -d _image + $(INKSCAPE) -l -o $@ $< + +.PHONY: all clean fetch gen commit diff --git a/documentation/cli.rst b/documentation/cli.rst index ece1903..d7abd3c 100644 --- a/documentation/cli.rst +++ b/documentation/cli.rst @@ -5,19 +5,19 @@ A basic set of command line tools are currently provided to facilitate testing a End users should prefer the CLI tools from the `pvAccessCPP `_ module for day to day use. -* pvxcall - analogous to pvcall -* pvxget - analogous to pvget -* pvxinfo - analogous to pvinfo -* pvxmonitor - analogous to pvmonitor or "pvget -m" -* pvxput - analogous to pvput -* pvxvct - UDP search/beacon Troubleshooting tool. +* ``pvxcall`` - analogous to ``pvcall`` +* ``pvxget`` - analogous to ``pvget`` +* ``pvxinfo`` - analogous to ``pvinfo`` +* ``pvxmonitor`` - analogous to ``pvmonitor`` or ``pvget -m`` +* ``pvxput`` - analogous to ``pvput`` +* ``pvxvct`` - UDP search/beacon Troubleshooting tool. Troubleshooting with Virtual Cable Tester ----------------------------------------- -The "pvxvct" executable is capable of listening for UDP searches from PVA clients, +The ``pvxvct`` executable is capable of listening for UDP searches from PVA clients, and/or UDP beacons from PVA servers. -Together with "pvxget" they can be used to investigate communications issues. +Together with ``pvxget`` they can be used to investigate communications issues. On the host with the PVA server (IOC or otherwise), run the following to listen for searches. :: @@ -37,14 +37,14 @@ as search requests are received. eg. :: 2020-04-09T19:37:01.146442772 INFO pvxvct "my:random:test:pvname" ... -Note that pvxvct does not use the **$EPICS_PVA*** environment variables -and by default listens on "0.0.0.0:5076". Sites using a non-default -port will need to add "-B 0.0.0.0:". +Note that pvxvct does not use the ``$EPICS_PVA*`` environment variables +and by default listens on ``0.0.0.0:5076``. Sites using a non-default +port will need to add ``-B 0.0.0.0:``. If searches are not seen, then investigate client :ref:`clientconf` -(**$EPICS_PVA*** environment variables), and firewall settings. +(``$EPICS_PVA*`` environment variables), and firewall settings. -If searches are seen, then switch to "pvxget -d ..." and a real PV name. +If searches are seen, then switch to ``pvxget -d ...`` and a real PV name. The output will be very verbose. Look for lines like the following: :: $ pvxget -d my:real:pv:name @@ -56,7 +56,7 @@ The output will be very verbose. Look for lines like the following: :: ... 2020-04-09T19:44:46.067255960 DEBUG pvxs.client.io Server 192.168.1.1:5075 accepts auth -Repeat with "pvxinfo" in place of "pvxget". +Repeat with ``pvxinfo`` in place of ``pvxget``. -If the "accepts auth" line is seen, but no subsequent error message, -then see :ref:`reportbug` and attach the output of "pvxget -d ...". +If the ``...accepts auth...`` line is seen, but no subsequent error message, +then see :ref:`reportbug` and attach the output of ``pvxget -d ...``. diff --git a/documentation/client.rst b/documentation/client.rst index 2a40e38..b151cd2 100644 --- a/documentation/client.rst +++ b/documentation/client.rst @@ -13,7 +13,7 @@ Client API Configuration ------------- -The recommended starting point is creating new context configured from $PVA_* environment variables. +The recommended starting point is creating new context configured from ``EPICS_PVA_*`` :ref:`environ`. Use `pvxs::client::Context::fromEnv`. EPICS_PVA_ADDR_LIST diff --git a/documentation/conf.py b/documentation/conf.py index b8a50fa..5ae5dc6 100644 --- a/documentation/conf.py +++ b/documentation/conf.py @@ -17,6 +17,7 @@ # sys.path.insert(0, os.path.abspath('.')) import time +import os def read_version(fmt): import os, re @@ -73,7 +74,7 @@ master_doc = 'index' # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. diff --git a/documentation/ioc.rst b/documentation/ioc.rst index 07b2193..a3f8571 100644 --- a/documentation/ioc.rst +++ b/documentation/ioc.rst @@ -9,14 +9,14 @@ IOC Integration The separate ``pvxsIoc`` library exists to run a PVXS server as part of an IOC. See also :ref:`includepvxs`. -IOC Integration respects the **$PVXS_LOG** as well as **$EPICS_PVA\*** environment variables. +IOC Integration respects the **$PVXS_LOG** as well as the **$EPICS_PVA\*** environment variables. Changes to this environment variable are possible prior to -calling ``\*_registerRecordDeviceDriver(pdbbase)``. +calling ``*_registerRecordDeviceDriver(pdbbase)``. IOC shell ^^^^^^^^^ -The "pvxsIoc" library adds several IOC shell functions which apply to all PVs +The ``pvxsIoc`` library adds several IOC shell functions which apply to all PVs served by the Integrated PVA server. .. cpp:function:: void pvxsr(int level) @@ -127,114 +127,11 @@ Currently supported format hints are: Group PV ^^^^^^^^ -By default no Group PVs are defined. +.. toctree:: + :maxdepth: 2 + :caption: Contents: -A Group PV is a mapping values taken from one or more Single PVs to be composed into an overall structure. - -A Group is defined using a JSON syntax. -Groups are defined with respect to a *Group Name*, which is also the PV name used when accessing the group. -Unlike records, the "field" of a group have a different meaning than the fields of a record. -Group field names are *not* part of the PV name. - -A group definition may be split among several records, or included in separate JSON file(s). -For example of a group including two records is: :: - - record(ai, "rec:X") { - info(Q:group, { - "grp:name": { - "X": {+channel:"VAL"} - } - }) - } - record(ai, "rec:Y") { - info(Q:group, { - "grp:name": { - "Y": {+channel:"VAL"} // .VAL in enclosing record() - } - }) - } - -Or equivalently with separate .db file and .json files. :: - - # some .db - record(ai, "rec:X") {} - record(ai, "rec:Y") {} - # in some .json - { - "grp:name": { - "X": {+channel:"rec:X.VAL"}, // full PV name - "Y": {+channel:"rec:Y.VAL"} - } - } - -This group, named ``grp:name``, has two group fields ``X`` and ``Y``. :: - - $ pvget grp:name - grp:name - structure - epics:nt/NTScalar:1.0 X - double value 0 - alarm_t alarm INVALID DRIVER UDF - time_t timeStamp 0 - ... - epics:nt/NTScalar:1.0 Y - double value 0 - alarm_t alarm INVALID DRIVER UDF - time_t timeStamp 0 - ... - -.. _groupjson: - -JSON Reference -^^^^^^^^^^^^^^ - -A Group `JSON schema `_ definition file is available. - -.. code-block:: json - - record(...) { - info(Q:group, { - "":{ - +id:"some/NT:1.0", // top level ID - +atomic:true, // whether monitors default to multi-locking atomicity - "":{ - +type:"scalar", // controls how map VAL mapped onto - +channel:"VAL", - +id:"some/NT:1.0", - +trigger:"*", // "*" or comma seperated list of s - +putorder:0, // set for fields where put is allowed, processing done in increasing order - }, - "": {+type:"meta", +channel:"VAL"} // special case adds meta-data fields at top level - } - }) - } - -Field mapping ``+type``: - -- ``scalar`` (default) places an NTScalar or NTScalarArray as a sub-structure. (see :ref:`ntscalar`) -- ``plain`` ignores all meta-data and places only the "value" as a field. - The field placed will have the type of the ``value`` field of the equivalent NTScalar/NTScalarArray as a field. -- ``any`` places a variant union into which the "value" is stored. -- ``meta`` places only the "alarm" and "timeStamp" fields of ``scalar``. -- ``structure`` places only the associated ``+id``. Has no ``+channel``. -- ``proc`` places no fields. The associated ``+channel`` is processed on PUT. - - -``+channel``: - -When included in an ``info(Q:group, ...``, the ``+channel`` must only name a field of the enclosing record. -(eg. ``+channel:"VAL"``) -When in a separate JSON file, ``+channel`` must be a full PV name, beginning with a record or alias name. -(eg. ``+channel:"record:name.VAL"``) - -Group ``+trigger``: - -The field triggers define how changes to the constituent field are translated into a subscription update to the group. -``+trigger`` may be an empty string (``""``), a wildcard ``"*"``, or a comma separated list of group field names. - -- ``""`` (the default) means that changes to the field do not cause a subscription update. -- ``"*"`` causes a subscription update containing the most recent values/meta-data of all group fields. -- A comma separated list of field names causes an update with the most recent values of only the listed group fields. + qgroup Access Security ^^^^^^^^^^^^^^^ diff --git a/documentation/netconfig.rst b/documentation/netconfig.rst index 7d0c6bb..c295cba 100644 --- a/documentation/netconfig.rst +++ b/documentation/netconfig.rst @@ -3,9 +3,6 @@ PVA Network Configuration ========================= -Also see Client :ref:`clientconf` and Server :ref:`serverconf` -for full lists of **EPICS_PVA*** environment varables. - Big Picture ----------- @@ -39,6 +36,47 @@ or when `pvxs::client::Context::hurryUp` is called. Server beacon destinations are by default configured using the client configuration. This may be overridden with **EPICS_PVAS_BEACON_ADDR_LIST** and **EPICS_PVAS_AUTO_BEACON_ADDR_LIST**. +.. _environ: + +Environment variables +--------------------- + +This table lists all of the ``$EPICS_PVA*`` environment variables understood by PVXS. +See Client :ref:`clientconf` and Server :ref:`serverconf` for detailed explanations. + ++----------------------------------+--------+--------+ +| Variable | Client | Server | ++==================================+========+========+ +| EPICS_PVA_ADDR_LIST | x | x | ++----------------------------------+--------+--------+ +| EPICS_PVAS_BEACON_ADDR_LIST | | x | ++----------------------------------+--------+--------+ +| EPICS_PVA_AUTO_ADDR_LIST | x | x | ++----------------------------------+--------+--------+ +| EPICS_PVAS_AUTO_BEACON_ADDR_LIST | | x | ++----------------------------------+--------+--------+ +| EPICS_PVAS_INTF_ADDR_LIST | | x | ++----------------------------------+--------+--------+ +| EPICS_PVA_SERVER_PORT | x | x | ++----------------------------------+--------+--------+ +| EPICS_PVAS_SERVER_PORT | | x | ++----------------------------------+--------+--------+ +| EPICS_PVA_BROADCAST_PORT | x | x | ++----------------------------------+--------+--------+ +| EPICS_PVAS_BROADCAST_PORT | | x | ++----------------------------------+--------+--------+ +| EPICS_PVAS_IGNORE_ADDR_LIST | | x | ++----------------------------------+--------+--------+ +| EPICS_PVA_CONN_TMO | | x | ++----------------------------------+--------+--------+ +| EPICS_PVA_NAME_SERVERS | x | | ++----------------------------------+--------+--------+ +| EPICS_PVA_BROADCAST_PORT | x | | ++----------------------------------+--------+--------+ +| EPICS_PVA_CONN_TMO | x | | ++----------------------------------+--------+--------+ + + .. _addrspec: Address Spec. @@ -72,38 +110,3 @@ Examples include: IPv6 multicast address, with Time To Live set to 1 (roughly equivalent to IPv4 broadcast). Send via the network interface named ``br0``. Use default port number. - -Environment variables ---------------------- - -+----------------------------------+--------+--------+ -| Variable | Client | Server | -+==================================+========+========+ -| EPICS_PVA_ADDR_LIST | x | x | -+----------------------------------+--------+--------+ -| EPICS_PVAS_BEACON_ADDR_LIST | | x | -+----------------------------------+--------+--------+ -| EPICS_PVA_AUTO_ADDR_LIST | x | x | -+----------------------------------+--------+--------+ -| EPICS_PVAS_AUTO_BEACON_ADDR_LIST | | x | -+----------------------------------+--------+--------+ -| EPICS_PVAS_INTF_ADDR_LIST | | x | -+----------------------------------+--------+--------+ -| EPICS_PVA_SERVER_PORT | x | x | -+----------------------------------+--------+--------+ -| EPICS_PVAS_SERVER_PORT | | x | -+----------------------------------+--------+--------+ -| EPICS_PVA_BROADCAST_PORT | x | x | -+----------------------------------+--------+--------+ -| EPICS_PVAS_BROADCAST_PORT | | x | -+----------------------------------+--------+--------+ -| EPICS_PVAS_IGNORE_ADDR_LIST | | x | -+----------------------------------+--------+--------+ -| EPICS_PVA_CONN_TMO | | x | -+----------------------------------+--------+--------+ -| EPICS_PVA_NAME_SERVERS | x | | -+----------------------------------+--------+--------+ -| EPICS_PVA_BROADCAST_PORT | x | | -+----------------------------------+--------+--------+ -| EPICS_PVA_CONN_TMO | x | | -+----------------------------------+--------+--------+ diff --git a/documentation/nt.rst b/documentation/nt.rst index af4a2ee..ba39c2a 100644 --- a/documentation/nt.rst +++ b/documentation/nt.rst @@ -53,6 +53,9 @@ Commonly used sub-structure to represent a time **"nanoseconds"** Number of nanoseconds since the start of the second. +**"timeStamp.userTag"** + Extra site-specific information which may provide context for the seconds/nanoseconds value. + .. _alarm_t: alarm_t diff --git a/documentation/nt_table1.svg b/documentation/nt_table1.svg new file mode 100644 index 0000000..e1c758e --- /dev/null +++ b/documentation/nt_table1.svg @@ -0,0 +1,257 @@ + + + + + + + + + + record(aai, "TST:Labels_") { field(FTVL, "STRING") field(NELM, "2") field(INP , {const: ["Label A", "Label B"]}) info(Q:group, { "TST:Tbl":{ +id:"epics:nt/NTTable:1.0", "labels":{+type: "plain", +channel: "VAL"} } })}record(aao, "TST:A") { ... }record(aao, "TST:B") { ... }record(longout, "TST:Save") { ... } + TST:Tbl epics:nt/NTTable:1.0 structure record structure _options boolean atomic true alarm_t alarm INVALID DRIVER UDF int severity 3 int status 2 string message UDF time_t timeStamp <undefined> long secondsPastEpoch 631152000 int nanoseconds 0 int userTag 0 string[] labels ["Label A", "Label B"] structure value double[] A [] double[] B [] + + + + + diff --git a/documentation/nt_table2.svg b/documentation/nt_table2.svg new file mode 100644 index 0000000..5582f51 --- /dev/null +++ b/documentation/nt_table2.svg @@ -0,0 +1,240 @@ + + + + + + + + + + record(aai, "TST:Labels_") { ... }record(aao, "TST:A") { field(FTVL, "DOUBLE") field(NELM, "10") info(Q:group, { "TST:Tbl":{ "value.A":{+type: "plain", +channel: "VAL", +putorder: 0} } })}record(aao, "TST:B") { ... }record(longout, "TST:Save") { ... } + TST:Tbl epics:nt/NTTable:1.0 structure record structure _options boolean atomic true alarm_t alarm INVALID DRIVER UDF int severity 3 int status 2 string message UDF time_t timeStamp <undefined> long secondsPastEpoch 631152000 int nanoseconds 0 int userTag 0 string[] labels ["Label A", "Label B"] structure value double[] A [] double[] B [] + + + + diff --git a/documentation/nt_table3.svg b/documentation/nt_table3.svg new file mode 100644 index 0000000..1b0b89e --- /dev/null +++ b/documentation/nt_table3.svg @@ -0,0 +1,277 @@ + + + + + + + + + + record(aai, "TST:Labels_") { ... }record(aao, "TST:A") { ... }record(aao, "TST:B") { field(FTVL, "DOUBLE") field(NELM, "10") info(Q:group, { "TST:Tbl":{ "":{+type:"meta", +channel: "VAL"}, "value.B":{+type: "plain", +channel: "VAL", +putorder: 1} } })}record(longout, "TST:Save") { ... } + TST:Tbl epics:nt/NTTable:1.0 structure record structure _options boolean atomic true alarm_t alarm INVALID DRIVER UDF int severity 3 int status 2 string message UDF time_t timeStamp <undefined> long secondsPastEpoch 631152000 int nanoseconds 0 int userTag 0 string[] labels ["Label A", "Label B"] structure value double[] A [] double[] B [] + + + + + + diff --git a/documentation/nt_table4.svg b/documentation/nt_table4.svg new file mode 100644 index 0000000..1734a2c --- /dev/null +++ b/documentation/nt_table4.svg @@ -0,0 +1,236 @@ + + + + + + + + + + record(aai, "TST:Labels_") { ... }record(aao, "TST:A") { ... }record(aao, "TST:B") { ... }record(longout, "TST:Save") { field(MDEL, "-1") field(TPRO, "1") info(Q:group, { "TST:Tbl":{ "_save":{+type: "proc", +channel: "VAL", +putorder: 2, +trigger: "*"} } })} + TST:Tbl epics:nt/NTTable:1.0 structure record structure _options boolean atomic true alarm_t alarm INVALID DRIVER UDF int severity 3 int status 2 string message UDF time_t timeStamp <undefined> long secondsPastEpoch 631152000 int nanoseconds 0 int userTag 0 string[] labels ["Label A", "Label B"] structure value double[] A [] double[] B [] + + + diff --git a/documentation/ntscalar.rst b/documentation/ntscalar.rst index 4b4a5b5..023cd4c 100644 --- a/documentation/ntscalar.rst +++ b/documentation/ntscalar.rst @@ -4,7 +4,7 @@ NTScalar and NTScalarArray ========================== -The "epics:nt/NTScalar:1.0" and related "epics:nt/NTScalarArray:1.0" +The ``epics:nt/NTScalar:1.0`` and related ``epics:nt/NTScalarArray:1.0`` definitions describe a primary 'value' and supporting meta-data. In the case of NTScalarArray the value is a 1-d array of primative type. In the case of NTScalar the value is a single primitive value. @@ -16,32 +16,65 @@ and optionally display and control meta-data fields. using namespace pvxs; - // integer scalar - auto iscalar = nt::NTScalar{TypeCode::Int64}.create(); - - // real array - auto farray = nt::NTScalar{TypeCode::Float64A}.create(); + // single integer + Value iscalar = nt::NTScalar{TypeCode::Int64}.create(); // eg. access "value" field iscalar["value"] = 42; +.. code-block:: c++ + + // Functionally equivalent pseudo-C++ + struct NTScalar_Int64 { + int64_t value; + struct alarm_t { + int64_t secondsPastEpoch; + int32_t nanoseconds; + int32_t userTag + } alarm; + struct time_t { + int32_t severity; + int32_t status; + std::string message + } timeStamp; + // if NTScalar::display + struct display_t { + int64_t limitLow, limitHigh; + std::string description, units; + // if NTScalar::form + int32_t precision; + struct enum_t { + int32_t index; + std::vector choices; + } form; + } display; + // if NTScalar::control + struct control_t { + int64_t limitLow, limitHigh; + } control; + // ... + }; + + auto iscalar = new NTScalar_Int64(); // not safe! + iscalar->value = 42; + Fields ------ **"value"** - The primary field. May be any pvxs::TypeCode other than Struct, Union, StructA, UnionA, or Null. + The primary field. May be any `pvxs::TypeCode` other than ``Struct``, ``Union``, ``StructA``, ``UnionA``, or ``Null``. -**"timeStamp.secondsPastEpoch"** +**"timeStamp.secondsPastEpoch"**, +**"timeStamp.nanoseconds"**, +**"timeStamp.userTag"** -**"timeStamp.nanoseconds"** - Time associated with the value. Typically time of measurement. See `time_t` - -**"alarm.severity"** - -**"alarm.status"** + See common :ref:`time_t`. +**"alarm.severity"**, +**"alarm.status"**, **"alarm.message"** - Alarm state associated with value. See `alarm_t` + + See common :ref:`alarm_t`. **"display.description"** Text providing some context about what this value/PV represents. @@ -64,9 +97,6 @@ Meta-data for numeric types. Ignore unless limitLow < limitHigh Not authoritative. -**"control.minStep"** - Hint for client of a useful minimum increment for setting. - **"valueAlarm.lowAlarmLimit"** **"valueAlarm.highAlarmLimit"** diff --git a/documentation/overview.rst b/documentation/overview.rst index 9c051cd..a4d7786 100644 --- a/documentation/overview.rst +++ b/documentation/overview.rst @@ -7,9 +7,11 @@ Basics What is EPICS? ^^^^^^^^^^^^^^ -https://docs.epics-controls.org/en/latest/guides/EPICS_Intro.html +`Overview `_ -https://epics.anl.gov/ +`epics-controls.org `_ + +`APS EPICS site `_ What is PVAccess? ^^^^^^^^^^^^^^^^^ diff --git a/documentation/qgroup.rst b/documentation/qgroup.rst new file mode 100644 index 0000000..8f95f49 --- /dev/null +++ b/documentation/qgroup.rst @@ -0,0 +1,257 @@ +.. _grouppv: + +QSRV Groups +########### + +By default no Group PVs are defined. + +A Group PV is a mapping of values stored in one or more database records +and made visible through a PVA structure. + +A Group is defined using a JSON syntax. +Groups are defined with respect to a *Group Name*, which is also the PV name used when accessing the group. +Unlike records, the "field" of a group have a different meaning than the fields of a record. +Group field names are *not* part of the PV name. + +A group definition may be split among several records, or included in separate JSON file(s). +For example of a group including two records is: :: + + record(ai, "rec:X") { + info(Q:group, { + "grp:name": { + "X": {+channel:"VAL"} + } + }) + } + record(ai, "rec:Y") { + info(Q:group, { + "grp:name": { + "Y": {+channel:"VAL"} # .VAL in enclosing record() + } + }) + } + +Or equivalently with separate .db file and .json files. +Use ``dbLoadGroup()`` to load .json files. :: + + # Store in some .db + record(ai, "rec:X") {} + record(ai, "rec:Y") {} + # Store in some .json + { + "grp:name": { + "X": {+channel:"rec:X.VAL"}, # full PV name + "Y": {+channel:"rec:Y.VAL"} + } + } + +This group, named ``grp:name``, has two group fields ``X`` and ``Y``. :: + + $ pvget grp:name + grp:name + structure + epics:nt/NTScalar:1.0 X + double value 0 + alarm_t alarm INVALID DRIVER UDF + time_t timeStamp 0 + ... + epics:nt/NTScalar:1.0 Y + double value 0 + alarm_t alarm INVALID DRIVER UDF + time_t timeStamp 0 + ... + +So ``pvget grp:name`` is compatible to ``pvget rec:X rec:Y`` with the added +benefit that with the former, values from the two records are read atomically on the server, +and delivered together. + +.. _groupjson: + +JSON Reference +============== + +A Group `JSON schema `_ definition file is available. +Keys beginning appear in contexts where a name may be either a data field name, +or a special key. + +.. code-block:: + + record(...) { + info(Q:group, { + "":{ + +id:"some/NT:1.0", # top level ID + +atomic:true, + "":{ + +type:"scalar", + +channel:"VAL", + +id:"another/NT:1.0", + +trigger:"*", + +putorder:0, + }, + # special case adds time/alarm meta-data fields + # at top level + "": {+type:"meta", +channel:"VAL"} + } + }) + } + +Mapping ``+type``: + +- ``scalar`` (default) places an NTScalar or NTScalarArray as a sub-structure. (see :ref:`ntscalar`) +- ``plain`` ignores all meta-data and places only the "value" as a field. + The field placed will have the type of the ``value`` field of the equivalent NTScalar/NTScalarArray as a field. +- ``any`` places a variant union into which the "value" is stored. +- ``meta`` places only the "alarm" and "timeStamp" fields of ``scalar``. +- ``structure`` places only the associated ``+id``. Has no ``+channel``. +- ``proc`` places no fields. The associated ``+channel`` is processed on PUT. + "proc" mappings will almost always set ``+putorder`` to control the relative + ordering of record processing. + + +Mapping ``+channel``: + +Most mapping ``+type`` require a ``+channel``. +The most common record field to map is ``+channel: "VAL"``. +When included in an ``info(Q:group, ...``, the ``+channel`` must only name a field of the enclosing record. +(eg. ``+channel:"VAL"``) +When in a separate JSON file, ``+channel`` must be a full PV name, beginning with a record or alias name. +(eg. ``+channel:"record:name.VAL"``) + +Mapping ``+trigger``: + +Triggers define when and which **changes to the constituent field are translated into a subscription update** to the group. +``+trigger`` may be set to: + +- ``""`` (default) means that changes to the field do not cause a subscription update. (see note below) +- ``"*"`` causes a subscription update containing the most recent values/meta-data of all group fields. +- A comma separated list of field names causes an update with the most recent values of only the listed group fields. + eg. ``+trigger: "value.A, value.B"``. + +For a new group definition, including records from one or more record processing chains, +the last record in that chain should have a ``+trigger`` mapping listing the group fields +updated by records in that chain. + +In the common case where a group is mapped to records in only one processing chain, +then the last mapped record in that chain should have ``+trigger: "*"``. + +.. note:: As a special case. A group with no ``+trigger`` mappings at all will function as if every mapping + includes a ``+trigger`` mapping for itself. + This is done so that such a situation does not cause confusion be posting no monitor updates at all. + However, this situation will almost never give desired behaviour as changes to records which + could otherwise be atomic will be split into multiple subscription updates. + +Mapping ``+putorder``: + +``+putorder`` must be set for a field to be writable through a group PV. +When more than one mapping has an order defined, the numeric value is used to define +the order in which the associated records are processed (in increasing order). + +Additionally, the values of ``+putorder`` also control the order of fields in the group PV definition. +This control is necessary only in limited cases, such as the ``NTTable`` specification, +where the iteration order of fields must match the order of the ``labels`` array. + +.. _understandinggroups: + +Understanding Groups +==================== + +NTTable Group Example +--------------------- + +One motivating use case for groups involves moving tabular data, encoded as "NTTable". +The overall goal of the ``TST:Tbl`` group PV is to take what would be a series of +discrete, non-atomic, network operations :: + + caput -a TST:A X 1 2 3 + caput -a TST:B X 5 6 7 + caput TST:Save.PROC 1 + +can instead be a single, atomic, network operation :: + + pvput TST:Tbl value.A='[1,2,3]' value.B='[5,6,7]' + +A subscriber sees: :: + + pvmonitor TST:Tbl + TST:Tbl INVALID DRIVER UDF + "Label A" "Label B" + TST:Tbl 2023-08-30 09:55:19.999 + "Label A" "Label B" + 1 5 + 2 6 + 3 7 + +This effect is achieved by mapping a table of two columns ("A" and "B") onto two ``aao`` records. +With a third record to take some action once both are updated. + +The following is meant to illustrate the mapping between the individual records, +and the fields of the group PV ``TST:Tbl``. + +On the left hand side are the contents of a file ``test.db``, +and on the right the output of ``pvget TST:Tbl``. + +.. raw:: html + +
+ +.. image:: _image/nt_table1.svg + +Here the ``TST:Labels_`` record contributes two mappings to the ``TST:Tbl`` Group. +(a record might contribute mappings to more than one group) + +This ``+id`` mapping contributes only the static type label string. +it could be attached to any of the four records, and is arbitrarily placed with this one. + +Necessarily, the ``label`` mappings provides the column labels of the "NTTable" definition. +So the record ``TST:Labels_`` field ``VAL`` is mapped into ``TST:Tbl`` as ``labels``. +With ``+type: "plain"``, this appears as a string array ("string[]"). + +.. raw:: html + +
+ +.. image:: _image/nt_table2.svg + +The ``TST:A`` record contributes only a ``value.A`` field. +With ``+type: "plain"``, this appears as a string array ("double[]"). + +The ``value.A`` mapping also sets ``+putorder: 0``, which is necessary to allow this group member +field to be changed through a PUT to the Group PV. +The numeric value controls the order in which records effected by a PUT are processed. +Processing occurs in order of increasing ``+putorder``. + +.. raw:: html + +
+ +.. image:: _image/nt_table3.svg + +The ``TST:B`` record contributes both a ``value.B`` field in the same manner as the A field/column. + +Additionally, it contributes a special ``""`` field with ``+type: "meta"``. +This exposes the ``TST:B.VAL`` timestamp and alarm meta-data as the sub-structure +fields ``alarm`` and ``timeStamp``. + +.. raw:: html + +
+ +.. image:: _image/nt_table4.svg + +The final record ``TST:Save`` does not contribute any data fields. +Instead it contributes a special ``+type: "proc"`` mapping. +Also, it contributes a ``+trigger`` mapping so that processing this record will +trigger a subscription/monitor update to all (changed) fields. + +Note, the field name ``_save`` only needs to be locally unique within the group, +but is otherwise ignored. + +Database Listing +^^^^^^^^^^^^^^^^ + +Loading the following with. :: + + dbLoadRecords("table.db", "N=TST:,LBL1=Label A,LBL2=Label B,PO1=0,PO2=1") + +.. literalinclude:: ../test/table.db + :linenos: diff --git a/documentation/releasenotes.rst b/documentation/releasenotes.rst index 321e4f6..d03149d 100644 --- a/documentation/releasenotes.rst +++ b/documentation/releasenotes.rst @@ -3,6 +3,17 @@ Release Notes ============= +1.2.3 (UNRELEASED) +------------------ + +* Add ``$PVXS_ENABLE_IPV6`` environment variable. Set ``PVXS_ENABLE_IPV6=NO`` to disable usage of IPv6. +* IOC: A warning is printed if a group definition includes no ``+trigger`` mappings. + This is likely a mis-configuration by omission which will produce more monitor updates than expected. +* `pvxs::server::ConnectOp::error` no longer blocks. +* Add `pvxs::target_information`. +* IOC: fill in ``display.precision``. +* Add :ref:`understandinggroups` section. + 1.2.2 (June 2023) ----------------- diff --git a/documentation/server.rst b/documentation/server.rst index 138d0a2..5223b7e 100644 --- a/documentation/server.rst +++ b/documentation/server.rst @@ -40,10 +40,10 @@ Configuration ------------- The recommended starting point when configuring a Server is `pvxs::server::Config::fromEnv` -which will use the following environment variables when set. +which will use the following :ref:`environ` when set. -Entries naming multiple environment variables will prefer the left most which is set. -eg. *EPICS_PVA_ADDR_LIST* is only checked if *EPICS_PVAS_BEACON_ADDR_LIST* is unset. +Entries naming multiple :ref:`environ` will prefer the left most which is set. +eg. ``EPICS_PVA_ADDR_LIST`` is only checked if ``EPICS_PVAS_BEACON_ADDR_LIST`` is unset. EPICS_PVAS_INTF_ADDR_LIST Space seperated list of local interface addresses to which the server will bind. @@ -84,7 +84,7 @@ EPICS_PVA_CONN_TMO All ***_ADDR_LIST** may contain IPv4 multicast, and IPv6 uni/multicast addresses. .. versionadded:: 0.2.0 - Prior to 0.2.0 *EPICS_PVA_CONN_TMO* was ignored. + Prior to 0.2.0 ``EPICS_PVA_CONN_TMO`` was ignored. .. doxygenstruct:: pvxs::server::Config :members: diff --git a/ioc/pvxs/iochooks.h b/ioc/pvxs/iochooks.h index 6559041..329a8b6 100644 --- a/ioc/pvxs/iochooks.h +++ b/ioc/pvxs/iochooks.h @@ -61,7 +61,8 @@ namespace ioc { * initHookRegister(&myinitHook); * } * extern "C" { - * epicsExportRegistrar(myregistrar); // needs matching entry in .dbd + * // needs matching "registrar(myregistrar)" in .dbd + * epicsExportRegistrar(myregistrar); * } * @endcode */