Confluence will be unusable 23-July-2024 at 06:00 due to a Crowd upgrade.
This is only a draft and is still under construction. Don't use this material until it is ready.
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.
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.
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
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.
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.
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 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.
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 ... )
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.".
CA | pvAccessCPP | PVXS |
---|---|---|
caget | pvget | pvxget |
camonitor | pvmonitor | pvxmonitor |
cainfo | pvinfo | pvxinfo |
caput | pvput | pvxput |
pvlist | pvxlist | |
pvcall | pvxcall | |
pvxvct |
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
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
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
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 }
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"