前言

EPICS V7 中引入了 pvAccess、pvData等相关模块,增加了对结构化数据的支持。 pvData (Process Variable Data, 过程变量数据) 是EPICS核心软件的一部分,它是一个运行时类型系统,具有用于处理结构化数据的序列化和内省(introspection)功能。

pvData有四种类型的数据字段:scalarscalarArraystructurestructureArrayscalar(标量)可以是以下标量类型之一:Boolean、Byte、Short、Int、Long、U(nsigned)Byte、Unsigned Short、Unsigned Int、Unsigned Long、Float、Double和String。scalarArray是一维数组,元素类型为任何标量类型。structure(结构体)是一组有序的字段,其中每个字段都有一个名称和类型。structureArray是结构体数组,由于字段可以是结构,因此可以创建复杂的结构。

QSRV是一个使用PVAccess协议的网络服务器,在EPICS IOC进程中运行,允许客户端请求访问其中的过程变量(PV)。 PVXS是一个PVAccess协议客户端/服务器的程序模块。功能等同于 pvDataCPP,并希望最终能取代CPP模块。

PVAccess 默认端口:5076

环境变量表:

VariableClientServer
EPICS_PVA_ADDR_LIST××
EPICS_PVAS_BEACON_ADDR_LIST×
EPICS_PVA_AUTO_ADDR_LIST××
EPICS_PVAS_AUTO_BEACON_ADDR_LIST×
EPICS_PVAS_INTF_ADDR_LIST×
EPICS_PVA_SERVER_PORT××
EPICS_PVAS_SERVER_PORT×
EPICS_PVA_BROADCAST_PORT××
EPICS_PVAS_BROADCAST_PORT×
EPICS_PVAS_IGNORE_ADDR_LIST×
EPICS_PVA_CONN_TMO××
EPICS_PVA_NAME_SERVERS×

快速使用

使用softIocPVA软件。

1
2
3
4
5
6
7
8
9
cat <<EOF > p2pexample.db
record(calc, "p2p:example:counter") {
    field(INPA, "p2p:example:counter")
    field(CALC, "A+1")
    field(SCAN, "1 second")
}
EOF

./bin/linux-x86_64/softIocPVA -d p2pexample.db

添加 QSRV 到 IOC

如果使用EPICS V7创建IOC,那么可以看到程序已经默认添加了QSRV到IOC中。如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# example/iocExampleApp/src/Makefile

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

# Finally link IOC to the EPICS Base libraries
iocExampleApp_LIBS += $(EPICS_BASE_IOC_LIBS)

编译运行IOC后,可直接使用pvgetpvputpvmonitorpvinfo等命令行工具访问过程变量。

添加 PVXS 到 IOC

需要先编译完成EPICS V7。

源码构建 PVXS 获取pvxs源码:

1
git clone --recursive https://github.com/epics-base/pvxs.git

配置EPICS_BASE环境变量:

1
2
3
cat <<EOF > pvxs/configure/RELEASE.local
EPICS_BASE=\$(TOP)/../epics-base
EOF

※ 编译libevent(可选):

1
make -C pvxs/bundle libevent # implies .$(EPICS_HOST_ARCH)

※ 交叉编译libevent(可选): 修改 bundle/Makefile

1
2
3
4
5
6
ifneq (,$(filter linux-%,$(EPICS_HOST_ARCH)))
# cross mingw hosted on linux
CMAKE_TOOLCHAIN_windows-x64-mingw ?= x86_64-w64-mingw32
# 添加下面一行
CMAKE_TOOLCHAIN_$(EPICS_HOST_ARCH) ?= $(EPICS_HOST_ARCH).cmake
endif

linux-loong64.cmake

1
2
3
4
5
6
7
8
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR AMD64)
set(CMAKE_C_COMPILER loongarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER loongarch64-linux-gnu-g++)
set(CMAKE_FIND_ROOT_PATH  /usr/linux-loong64 )
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

交叉编译:

1
make -C pvxs/bundle libevent.linux-loong64 EPICS_HOST_ARCH=linux-loong64

命令行工具:

  • pvxcall - 与 pvcall 相似
  • pvxget - 与 pvget 相似
  • pvxinfo - 与 pvinfo 相似
  • pvxmonitor - 与 pvmonitor 或 pvget -m 相似
  • pvxput - 与 pvput 相似
  • pvxvct - UDP search/beacon Troubleshooting tool.

添加 PVXS 到 IOC

配置EPICS_BSAEPVXS

1
2
3
4
cat <<EOF >> configure/RELEASE.local
EPICS_BASE=/path/to/your/build/of/epics-base
PVXS=/path/to/your/build/of/pvxs
EOF

pvxspvxsIoc作为依赖库添加到IOC:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# example/iocExampleApp/src/Makefile

# Link PVXS if available
ifdef PVXS_MAJOR_VERSION
    iocExampleApp_LIBS += pvxsIoc pvxs
    iocExampleApp_DBD += pvxsIoc.dbd
else
# Link QSRV (pvAccess Server) if available
ifdef EPICS_QSRV_MAJOR_VERSION
    iocExampleApp_LIBS += qsrv
    iocExampleApp_LIBS += $(EPICS_BASE_PVA_CORE_LIBS)
    iocExampleApp_DBD += PVAServerRegister.dbd
    iocExampleApp_DBD += qsrv.dbd
endif
endif

# Finally link IOC to the EPICS Base libraries
iocExampleApp_LIBS += $(EPICS_BASE_IOC_LIBS)

pvxsIoc只应包含在IOC中,在编写应用程序时只应该依赖pvxs

编译运行

1
make -j 8

QSRV

单个 PV “单个”PV是由CA服务器(RSRV)提供的记录名字,所有记录字段都可以被访问。因此,所有可通过CA访问的数据也可通过PVAccess访问。 QSRV将所有“单个”PV呈现为符合规范类型NTScalarNTScalarArrayNTEnum的结构,具体取决于原生DBF字段类型。

定义PV组 “组”是使用JSON语法定义的,组名称也是PV名称。与“记录”不同:

  • Records have fields (记录拥有字段)
  • Channels/PVs have properties (通道/PV 拥有属性) 组定义可以在多个记录中拆分,参考example/iocExampleApp/Db/circle.db,或者包含在单独的JSON文件中,参考JSON reference

拆分写法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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()
        }
    })
}

JSON文件写法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 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"}
    }
}

加载JSON文件:

1
2
3
# void dbLoadGroup(const char *file, const char *macros)
# Load Group definitions from a separate JSON file. eg.
dbLoadGroup "db/some.json", "user=root"

PVAccess 链接

PVA 链接 JSON schema

例:

1
2
3
4
record(longin, "tgt") {}
record(longin, "src") {
    field(INP, {pva:{pv:"tgt"}})
}

或:

1
2
3
4
5
6
record(longout, "src") {
    field(INP, {pva:{
        pv:"target:pv",
        proc:"CP"
    }})
}

参考链接