This is only a draft and is still under construction. Don't use this material until it is ready.

Requirements

We expect that the reader has experience with EPICS 3 to be able to follow this tutorial.

The reader must have access to a building machine available at SLAC's network, with RHEL7 or above. We are using aird-b50-srv01 to build and run the examples in this tutorial.

We assume that the reader has the standard SLAC environment variables set correctly, like $EPICS_MODULES_TOP and $EPICS_BASE.

Create an IOC app and add PVXS to it

Building an IOC application from scratch

Choose a directory of your preference to start building the IOC. Your directory will be referenced as $MY_HOME in this tutorial. Create the directory $MY_HOME/pvxsTutorial. cd to this directory.

$ makeBaseApp.pl -t example pvxsTutorial
$ makeBaseApp.pl -i -t example pvxsTutorial-ioc

Choose the appropriate architecture and give the app name "pvxsTutorial" when asked.

Use make to build this IOC application.

Make st.cmd executable:

$ chmod +x iocBoot/pvxsTutorial-ioc/st.cmd

We recommend that you have 3 terminal sessions opened. tmux can be helpful for this. These are the recommended uses for the terminals.

  1. At the application $TOP to run make and edit files in the IOC application.
  2. At $MY_HOME/pvxsTutorial/iocBoot/pvxsTutorial-ioc/ to run ./st.cmd and stop when needed.
  3. At $EPICS_MODULES_TOP/pvxs/R1.2.2-0.2.0/bin/rhel7-x86_64 to run the pvxs binaries. Change to the directory of your architecture if not rhel7.

Run IOC and check PVs with all three "gets"

Remember that makeBaseApp.pl with type example gives you ready-to-use PVs that start with your username. Keep it this way because we are using the environment variable $USER in the commands along the tutorial and this will work for everyone.

Run your IOC directly issuing ./st.cmd in the building server you've been using. You can check the available PVs with dbl.

In terminal 3 (see list of suggested terminals before), run all three forms of "gets" for the PV $USER:calcExample. In the output throughout the tutorial, my username "marcio" will be shown everywhere. Yours will be different, of course.

$ caget $USER:calcExample
marcio:calcExample             0

$ pvget $USER:calcExample
marcio:calcExample 2024-02-26 15:21:44.467  6 MINOR DEVICE HIGH

$ ./pvxget $USER:calcExample
marcio:calcExample
    value double = 2
    alarm.severity int32_t = 2
    alarm.status int32_t = 1
    alarm.message string = "LOLO"
    timeStamp.secondsPastEpoch int64_t = 1708989770
    timeStamp.nanoseconds int32_t = 467369898
    display.limitLow double = 0
    display.limitHigh double = 10
    display.description string = "Counter"
    display.units string = "Counts"
    display.precision int32_t = 0
    display.form.index int32_t = 0
    display.form.choices string[] = {7}["Default", "String", "Binary", "Decimal", "Hex", "Exponential", "Engineering"]
    control.limitLow double = 0
    control.limitHigh double = 10
    valueAlarm.lowAlarmLimit double = 2
    valueAlarm.lowWarningLimit double = 4
    valueAlarm.highWarningLimit double = 6
    valueAlarm.highAlarmLimit double = 8
  • caget is the decades-long EPICS 3 client to retrieve the PV's value.
  • pvget is the equivalent for EPICS 7, built using PVAccessCPP, which is part of EPICS base.
  • pvxget is the equivalent for PVXS. In the PVXS page it is said that "A basic set of command line tools are currently provided to facilitate testing and development. End users should prefer the CLI tools from the pvAccessCPP module for day-to-day use.". We will try them as an exercise but it seems it is not in the PVXS roadmap to replace the command line tools from PVAccessCPP with the PVXS ones.

Cleaning QSRV1

Looks like we can access the PV with EPICS 7 clients already. How this is possible?

makeBaseApp.pl -t example at SLAC was made to bring an IOC application integrated with QSRV. You can check this by looking at $TOP/pvxsTutorialApp/src/Makefile. You will see these lines:

# Link QSRV (pvAccess Server) if available
ifdef EPICS_QSRV_MAJOR_VERSION
    pvxsExample_LIBS += qsrv
    pvxsExample_LIBS += $(EPICS_BASE_PVA_CORE_LIBS)
    pvxsExample_DBD += PVAServerRegister.dbd
    pvxsExample_DBD += qsrv.dbd
endif

Comment all these lines, rebuild the IOC app, reboot the IOC, and check the PV with caget, pvget, and pvxget again:

$ caget $USER:calcExample
marcio:calcExample             0

$ pvget $USER:calcExample
Timeout
marcio:calcExample

$ ./pvxget $USER:calcExample
Timeout with 1 outstanding

So, it only works with caget. With this exercise you can see one of the biggest features of QSRV: make all EPICS records loaded in the IOC available through PV Access. This way the IOC can communicate with either Channel Access (CA) or PV Access (PVA). We will deep dive into the other features.

Leave the lines commented out in Makefile. We will add PVXS to replace it.

Adding PVXS to the IOC app

Open $TOP/configure/RELEASE and add these lines:

PVXS_MODULE_VERSION=R1.2.2-0.2.0

PVXS=$(EPICS_MODULES)/pvxs/$(PVXS_MODULE_VERSION)

If this version of PVXS is deprecated when you are reading this tutorial, replace it with a newer version.

Open $TOP/pvxsTutorialApp/src/Makefile. Add these lines to the appropriate places:

pvxsTutorial_DBD += pvxsIoc.dbd
pvxsTutorial_LIBS += pvxsIoc pvxs
pvxsTutorial_LIBS += Com

If you are using PVXS lower than 1.3, add these lines to st.cmd, before iocInit:

# Default with PVXS >= 1.3.0
# Needed with PVXS 1.2.x
epicsEnvSet("PVXS_QSRV_ENABLE", "YES")

Rebuild the IOC application and restart the IOC. PVXS brings QSRV2 with it, so all 3 "gets" should be retrieving data again.

Exploring PVXS built-in tools

IOC shell commands

pvxsr

PVXS Server Report. Shows information about server configuration (level==0) or about connected clients (level>0). While in the IOC shell console, call pvxsr with no arguments to see the server configuration:

epics> pvxsr
EPICS_PVAS_AUTO_BEACON_ADDR_LIST=NO
EPICS_PVAS_BEACON_ADDR_LIST=134.79.219.255:5076 255.255.255.255:5076
EPICS_PVAS_BROADCAST_PORT=5076
EPICS_PVAS_IGNORE_ADDR_LIST=
EPICS_PVAS_INTF_ADDR_LIST=0.0.0.0
EPICS_PVAS_SERVER_PORT=5075
Source: __builtin prio=-1 
Source: __server prio=-1 
Source: qsrvSingle prio=0 
Source: qsrvGroup prio=1

To see the PVs handled by PVXS, increase the level number:

epics> pvxsr 5
EPICS_PVAS_AUTO_BEACON_ADDR_LIST=NO
EPICS_PVAS_BEACON_ADDR_LIST=134.79.219.255:5076 255.255.255.255:5076
EPICS_PVAS_BROADCAST_PORT=5076
EPICS_PVAS_IGNORE_ADDR_LIST=
EPICS_PVAS_INTF_ADDR_LIST=0.0.0.0
EPICS_PVAS_SERVER_PORT=5075
Source: __builtin prio=-1 StaticProvider
Source: __server prio=-1 Source
Source: qsrvSingle prio=0 IOC
    marcio:aSubExample
    marcio:ai1
    marcio:ai2
    marcio:ai3
    marcio:aiExample
    marcio:aiExample1
    marcio:aiExample2
    marcio:aiExample3
    marcio:calc1
    marcio:calc2
    marcio:calc3
    marcio:calcExample
    marcio:calcExample1
    marcio:calcExample2
    marcio:calcExample3
    marcio:circle:angle
    marcio:circle:period
    marcio:circle:step
    marcio:circle:tick
    marcio:circle:x
    marcio:circle:y
    marcio:compressExample
    marcio:line:a
    marcio:line:b
    marcio:pvxsTutorial:version
    marcio:subExample
    marcio:xxxExample
Source: qsrvGroup prio=1 IOC
    marcio:circle
    marcio:line
State: Running

One cool thing is checking who is monitoring the PVs with EPICS clients. In one terminal run "./pvxmonitor $USER:calcExample $USER:aiExample". While the monitor runs, in the EPICS IOC shell console:

epics> pvxsr 5
EPICS_PVAS_AUTO_BEACON_ADDR_LIST=NO
EPICS_PVAS_BEACON_ADDR_LIST=134.79.219.255:5076 255.255.255.255:5076
EPICS_PVAS_BROADCAST_PORT=5076
EPICS_PVAS_IGNORE_ADDR_LIST=
EPICS_PVAS_INTF_ADDR_LIST=0.0.0.0
EPICS_PVAS_SERVER_PORT=5075
Source: __builtin prio=-1 StaticProvider
Source: __server prio=-1 Source
Source: qsrvSingle prio=0 IOC
    marcio:aSubExample
    marcio:ai1
    marcio:ai2
    marcio:ai3
    marcio:aiExample
    marcio:aiExample1
    marcio:aiExample2
    marcio:aiExample3
    marcio:calc1
    marcio:calc2
    marcio:calc3
    marcio:calcExample
    marcio:calcExample1
    marcio:calcExample2
    marcio:calcExample3
    marcio:circle:angle
    marcio:circle:period
    marcio:circle:step
    marcio:circle:tick
    marcio:circle:x
    marcio:circle:y
    marcio:compressExample
    marcio:line:a
    marcio:line:b
    marcio:pvxsTutorial:version
    marcio:subExample
    marcio:xxxExample
Source: qsrvGroup prio=1 IOC
    marcio:circle
    marcio:line
State: Running
    Peer[::ffff:134.79.216.60]:33484 backlog=0 TX=1986 RX=212 auth=ca
ca/marcio@[::ffff:134.79.216.60]:33484        marcio:calcExample TX=951 RX=46         Executing ioid=268443648 MONITOR
        marcio:aiExample TX=956 RX=46         Executing ioid=268443649 MONITOR

It now shows the registered monitors to this IOC.

pvxsl

pvxsl works the same as dbl, showing the list of records that are being served by PVXS. If you diff the output of pvxsl and dbl, you will see differences on the list:

epics> dbl > dbl_output
epics> pvxsl > pvxsl_output

From the application $TOP:

$ diff <(sort iocBoot/pvxsTutorial-ioc/dbl_output) <(sort iocBoot/pvxsTutorial-ioc/pvxsl_output)
15a16
> marcio:circle
22a24
> marcio:line

So, marcio:circle and marcio:line are available only for PVXS and not for CA. Let's confirm this:

$ pvget marcio:circle marcio:line

(... many lines of output ...)

$ caget marcio:circle marcio:line
Channel connect timed out: some PV(s) not found.

If you add a level argument to pvxsl it will show the attached sources for the PV names:

epics> pvxsl 1
------------------
SOURCE: qsrvSingle@0
------------------
RECORDS: 
  marcio:aSubExample
  marcio:ai1
  marcio:ai2
  marcio:ai3
  marcio:aiExample
  marcio:aiExample1
  marcio:aiExample2
  marcio:aiExample3
  marcio:calc1
  marcio:calc2
  marcio:calc3
  marcio:calcExample
  marcio:calcExample1
  marcio:calcExample2
  marcio:calcExample3
  marcio:circle:angle
  marcio:circle:period
  marcio:circle:step
  marcio:circle:tick
  marcio:circle:x
  marcio:circle:y
  marcio:compressExample
  marcio:line:a
  marcio:line:b
  marcio:pvxsTutorial:version
  marcio:subExample
  marcio:xxxExample
------------------
SOURCE: qsrvGroup@1 [dynamic]
------------------
RECORDS: 
  marcio:circle
  marcio:line

marcio:circle and marcio:line are being sourced by qsrvGroup while all the others are sourced by qsrvSingle. That may be the explanation for why we can't retrieve circle and line with caget. The CA server can't handle PVs from qsrvGroup. We will learn about grouping PVs later in this tutorial.

pvxsi

This IOC shell command prints several lines with information that can be useful for diagnostics.

epics> pvxsi                                                                                                                               
Host: rhel7-x86_64                                                                                                                         
Target: rhel7-x86_64 Linux gcc                                                                                                             
Toolchain                                                                                                                                  
    __cplusplus = 201103                                                                                                                   
    GCC 4.8.5
    GLIBC 2.17
    __GLIBCXX__ 20150623
Versions
    PVXS 1.2.2 (R1.2.2-0.2.0)
    EPICS 7.0.3.1-1.0.6
    libevent 2.2.0-alpha-dev
Runtime
    uname() -> Linux aird-b50-srv01 3.10.0-1160.90.1.el7.x86_64 #1 SMP Fri Mar 17 08:39:44 UTC 2023 x86_64
    epicsThreadGetCPUs() -> 32
    osiLocalAddr() -> 134.79.216.60

( ... etc ... )

Command line tools

There are many similarities between CA, pvAccessCPP, and PVXS command line tools. I'm finding the PVXS output more verbose and preferring to use the pvAccessCPP version, instead. Maybe that's why the PVXS author says "A basic set of command line tools are currently provided to facilitate testing and development. End users should prefer the CLI tools from the pvAccessCPP module for day to day use.".

CApvAccessCPPPVXS
cagetpvgetpvxget
camonitorpvmonitorpvxmonitor
cainfopvinfopvxinfo
caputpvputpvxput

pvlistpvxlist

pvcallpvxcall


pvxvct

pvxget

Reads a PV. Pretty much the same as what caget does. pvxget is very verbose.

$ pvget marcio:calc1
marcio:calc1 2024-03-07 15:29:58.490  0 MAJOR DEVICE LOL
$ ./pvxget marcio:calc1
marcio:calc1
    value double = 0
    alarm.severity int32_t = 2
    alarm.status int32_t = 1
    alarm.message string = "LOLO"
    timeStamp.secondsPastEpoch int64_t = 1709854198
    timeStamp.nanoseconds int32_t = 489760636
    display.limitLow double = 0
    display.limitHigh double = 10
    display.description string = "Counter No. 1"
    display.units string = "Counts"
    display.precision int32_t = 0
    display.form.index int32_t = 0
    display.form.choices string[] = {7}["Default", "String", "Binary", "Decimal", "Hex", "Exponential", "Engineering"]
    control.limitLow double = 0
    control.limitHigh double = 10
    valueAlarm.lowAlarmLimit double = 2
    valueAlarm.lowWarningLimit double = 4
    valueAlarm.highWarningLimit double = 6
    valueAlarm.highAlarmLimit double = 8

pvxmonitor

The same as what camonitor does. pvxmonitor is very verbose.

$ pvmonitor marcio:calc1
marcio:calc1 2024-03-07 15:35:17.490  9 MAJOR DEVICE HIHI
marcio:calc1 2024-03-07 15:35:18.490  0 MAJOR DEVICE LOLO
marcio:calc1 2024-03-07 15:35:19.490  1 MAJOR DEVICE LOLO
marcio:calc1 2024-03-07 15:35:20.490  2 MAJOR DEVICE LOLO
marcio:calc1 2024-03-07 15:35:21.490  3 MINOR DEVICE LOW
marcio:calc1 2024-03-07 15:35:22.490  4 MINOR DEVICE LOW
^C
$ ./pvxmonitor marcio:calc1
marcio:calc1 Connected to 134.79.216.60:5075
marcio:calc1
    value double = 9
    alarm.severity int32_t = 2
    alarm.status int32_t = 1
    alarm.message string = "HIHI"
    timeStamp.secondsPastEpoch int64_t = 1709854617
    timeStamp.nanoseconds int32_t = 489745343
    display.limitLow double = 0
    display.limitHigh double = 10
    display.description string = "Counter No. 1"
    display.units string = "Counts"
    display.precision int32_t = 0
    display.form.index int32_t = 0
    display.form.choices string[] = {7}["Default", "String", "Binary", "Decimal", "Hex", "Exponential", "Engineering"]
    control.limitLow double = 0
    control.limitHigh double = 10
    valueAlarm.lowAlarmLimit double = 2
    valueAlarm.lowWarningLimit double = 4
    valueAlarm.highWarningLimit double = 6
    valueAlarm.highAlarmLimit double = 8
marcio:calc1
    value double = 0
    alarm.severity int32_t = 2
    alarm.status int32_t = 1
    alarm.message string = "LOLO"
    timeStamp.secondsPastEpoch int64_t = 1709854618
    timeStamp.nanoseconds int32_t = 489744568
marcio:calc1
    value double = 1
    alarm.severity int32_t = 2
    alarm.status int32_t = 1
    alarm.message string = "LOLO"
    timeStamp.secondsPastEpoch int64_t = 1709854619
    timeStamp.nanoseconds int32_t = 489745640
marcio:calc1
    value double = 2
    alarm.severity int32_t = 2
    alarm.status int32_t = 1
    alarm.message string = "LOLO"
    timeStamp.secondsPastEpoch int64_t = 1709854620
    timeStamp.nanoseconds int32_t = 489746607
^C

pvxput

The same as what caput does. Interestingly, pvxput doesn't print anything after running the command successfully. Here, it is less verbose than pvput.

$ pvput marcio:xxxExample 1
Old : <undefined>              0 INVALID DRIVER UDF
New : 2024-03-07 15:43:14.545  1 MAJOR DEVICE LOL
$ ./pvxput marcio:xxxExample 2

pvxinfo

Similar to what cainfo does. The format output of pvinfo and pvxinfo are different, although the content is the same.

$ pvinfo marcio:calc1
marcio:calc1
Server: 134.79.216.60: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
        structure display
            double limitLow
            double limitHigh
            string description
            string units
            int precision
            enum_t form
                int index
                string[] choices
        structure control
            double limitLow
            double limitHigh
            double minStep
        structure valueAlarm
            boolean active
            double lowAlarmLimit
            double lowWarningLimit
            double highWarningLimit
            double highAlarmLimit
            int lowAlarmSeverity
            int lowWarningSeverity
            int highWarningSeverity
            int highAlarmSeverity
            double hysteresis
$ ./pvxinfo marcio:calc1
marcio:calc1 from 134.79.216.60:5075
struct "epics:nt/NTScalar:1.0" {
    double value
    struct "alarm_t" {
        int32_t severity
        int32_t status
        string message
    } alarm
    struct "time_t" {
        int64_t secondsPastEpoch
        int32_t nanoseconds
        int32_t userTag
    } timeStamp
    struct {
        double limitLow
        double limitHigh
        string description
        string units
        int32_t precision
        struct "enum_t" {
            int32_t index
            string[] choices
        } form
    } display
    struct {
        double limitLow
        double limitHigh
        double minStep
    } control
    struct {
        bool active
        double lowAlarmLimit
        double lowWarningLimit
        double highWarningLimit
        double highAlarmLimit
        int32_t lowAlarmSeverity
        int32_t lowWarningSeverity
        int32_t highWarningSeverity
        int32_t highAlarmSeverity
        double hysteresis
    } valueAlarm
}

pvxlist

This command doesn't have an equivalent for Channel Access. You can use it to bring information on IOCs running in the network and also the list of PVs available for each IOC. I liked more the pvxlist command instead of pvlist.

pvxlist alone brings all IOCs that are reachable from the network:

$  pvlist
GUID 0x0675E765000000004A485F3A version 2: tcp@[ 134.79.217.33:46741 ]
GUID 0x124BE36500000000319D180B version 2: tcp@[ 134.79.216.44:5075 ]
GUID 0x1542E6650000000059733F0B version 2: tcp@[ 134.79.217.42:43721 ]
GUID 0x1942E665000000007D383B18 version 2: tcp@[ 134.79.217.42:47251 ]
GUID 0x1B42E66500000000A751A502 version 2: tcp@[ 134.79.217.42:50557 ]
GUID 0x23C6137BFAE431FC003C09CB version 2: tcp@[ 134.79.217.195:5075 ]
GUID 0x314E1DCE67F61F23CB3C0282 version 2: tcp@[ 134.79.217.80:38651 ]
GUID 0x368BE765000000002B13A229 version 2: tcp@[ 134.79.219.136:55597 ]
GUID 0x3D08E6650000000091C0C238 version 2: tcp@[ 134.79.218.203:37397 ]
GUID 0x3E08E665000000008436C20F version 2: tcp@[ 134.79.218.203:53311 ]

( ... etc ... )
$  ./pvxlist
134.79.216.60:5075
134.79.217.33:5075
134.79.217.42:44749
134.79.217.42:5075
134.79.217.42:38321
134.79.217.80:38651
134.79.217.151:5075
134.79.218.77:44281
134.79.218.77:44147
134.79.217.195:5075
134.79.217.111:5075

( ... etc ... )

If you know the server where your IOC is running, you can get the list of PVs available. In this case, both commands output data in the same format.

$  pvlist 134.79.216.60
marcio:aSubExample
marcio:ai1
marcio:ai2
marcio:ai3
marcio:aiExample
marcio:aiExample1
marcio:aiExample2
marcio:aiExample3

( ... etc ... )
$   ./pvxlist 134.79.216.60
marcio:aSubExample
marcio:ai1
marcio:ai2
marcio:ai3
marcio:aiExample
marcio:aiExample1
marcio:aiExample2
marcio:aiExample3

( ... etc ... )

With the -i argument the command brings information about the server:

$   pvlist -i 134.79.216.60
structure
    string implLang cpp
    string version PVXS 1.2.2 (R1.2.2-0.2.0)
$  ./pvxlist -i 134.79.216.60
134.79.216.60 version="PVXS 1.2.2 (R1.2.2-0.2.0)" lang="cpp"
  • No labels