Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8fa231352d | |||
| 949b3f63c2 | |||
| 3a08da445b | |||
| 61682f72fe | |||
| 4cb3d4d696 | |||
| 0af5e972f3 | |||
| 2a53ea7e47 | |||
| 3b9990e365 | |||
| db5af06e07 | |||
| 5c75fd7bfc | |||
| 1c7846fa4b | |||
| cdee0b36f3 | |||
| db5cf25c4d | |||
| 26f55cf6ce | |||
| 397a061d72 | |||
| 3df05bf1ce | |||
| b9ce6cbd6e | |||
| bb85647a12 | |||
| dc22a4a4e3 | |||
| 5a81763fb8 | |||
| 122e9bdbb4 | |||
| cb1b41c1b2 | |||
| 69a7bb3795 | |||
| c95d9a68fe | |||
| 8be883f98b | |||
| b3d37e7f39 | |||
| c5cba4ccc4 | |||
| f07d14b12d | |||
| 3f38e616ee | |||
| 61ec0715be |
@@ -19,7 +19,7 @@ env:
|
||||
EPICS_TEST_IMPRECISE_TIMING: YES
|
||||
|
||||
jobs:
|
||||
build-base:
|
||||
native:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
# Set environment variables from matrix parameters
|
||||
@@ -64,12 +64,6 @@ jobs:
|
||||
extra: "CMD_CXXFLAGS=-std=c++11"
|
||||
name: "7.0 Ub-20 gcc-9 C++11, static"
|
||||
|
||||
- os: ubuntu-16.04
|
||||
cmp: clang
|
||||
configuration: default
|
||||
base: "7.0"
|
||||
name: "7.0 Ub-16 clang-9"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: clang
|
||||
configuration: default
|
||||
@@ -91,27 +85,6 @@ jobs:
|
||||
rtems: "4.9"
|
||||
name: "7.0 Ub-20 gcc-9 + RT-4.9"
|
||||
|
||||
- os: ubuntu-16.04
|
||||
cmp: gcc-4.8
|
||||
utoolchain: "4.8"
|
||||
configuration: default
|
||||
base: "7.0"
|
||||
name: "7.0 Ub-16 gcc-4.8"
|
||||
|
||||
- os: ubuntu-16.04
|
||||
cmp: gcc-4.9
|
||||
utoolchain: "4.9"
|
||||
configuration: default
|
||||
base: "7.0"
|
||||
name: "7.0 Ub-16 gcc-4.9"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc-8
|
||||
utoolchain: "8"
|
||||
configuration: default
|
||||
base: "7.0"
|
||||
name: "7.0 Ub-20 gcc-8"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: clang
|
||||
configuration: default
|
||||
@@ -146,24 +119,11 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Cache Dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache
|
||||
key: ${{ matrix.base }}/${{ matrix.os }}/${{ matrix.cmp }}/${{ matrix.configuration }}/${{ matrix.wine }}${{ matrix.rtems }}/${{ matrix.extra }}/${{ hashFiles('.github/workflows/ci-scripts-build.yml') }}
|
||||
- name: "apt-get install"
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install qemu-system-x86 g++-mingw-w64-x86-64 gdb
|
||||
if: runner.os == 'Linux'
|
||||
- name: "apt-get install gcc-${{ matrix.utoolchain }}"
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install software-properties-common
|
||||
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install g++-${{ matrix.utoolchain }}
|
||||
if: matrix.utoolchain
|
||||
- name: Prepare and compile dependencies
|
||||
run: python .ci/cue.py prepare
|
||||
- name: Build main module
|
||||
@@ -171,9 +131,86 @@ jobs:
|
||||
- name: Run main module tests
|
||||
run: python .ci/cue.py test
|
||||
- name: Upload tapfiles Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tapfiles ${{ matrix.name }}
|
||||
path: '**/O.*/*.tap'
|
||||
if-no-files-found: ignore
|
||||
- name: Collect and show test results
|
||||
run: python .ci/cue.py test-results
|
||||
|
||||
docker:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ matrix.image }}
|
||||
# Set environment variables from matrix parameters
|
||||
env:
|
||||
BASE: ${{ matrix.base }}
|
||||
CMP: ${{ matrix.cmp }}
|
||||
BCFG: ${{ matrix.configuration }}
|
||||
WINE: ${{ matrix.wine }}
|
||||
RTEMS: ${{ matrix.rtems }}
|
||||
EXTRA: ${{ matrix.extra }}
|
||||
TEST: ${{ matrix.test }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Job names also name artifacts, character limitations apply
|
||||
include:
|
||||
- name: Linux centos 7
|
||||
image: centos:7
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
base: "7.0"
|
||||
|
||||
- name: Linux fedora latest
|
||||
image: fedora:latest
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
base: "7.0"
|
||||
|
||||
steps:
|
||||
- name: "Build newer Git"
|
||||
# actions/checkout@v2 wants git >=2.18
|
||||
# centos:7 has 1.8
|
||||
if: matrix.image=='centos:7'
|
||||
run: |
|
||||
yum -y install curl make gcc curl-devel expat-devel gettext-devel openssl-devel zlib-devel perl-ExtUtils-MakeMaker
|
||||
curl https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.29.0.tar.gz | tar -xz
|
||||
cd git-*
|
||||
make -j2 prefix=/usr/local all
|
||||
make prefix=/usr/local install
|
||||
cd ..
|
||||
rm -rf git-*
|
||||
type -a git
|
||||
git --version
|
||||
- name: "Redhat setup"
|
||||
run: |
|
||||
dnfyum() {
|
||||
dnf -y "$@" || yum -y "$@"
|
||||
return $?
|
||||
}
|
||||
dnfyum install python3 gdb make perl gcc-c++ glibc-devel readline-devel ncurses-devel perl-devel libevent-devel net-tools
|
||||
git --version || dnfyum install git
|
||||
# rather than just bite the bullet and link python3 -> python,
|
||||
# people would rather just break all existing scripts...
|
||||
[ -e /usr/bin/python ] || ln -sf /usr/bin/python3 /usr/bin/python
|
||||
python --version
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Prepare and compile dependencies
|
||||
run: python .ci/cue.py prepare
|
||||
- name: Build main module
|
||||
run: python .ci/cue.py build
|
||||
- name: Run main module tests
|
||||
run: python .ci/cue.py test
|
||||
- name: Upload tapfiles Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tapfiles ${{ matrix.name }}
|
||||
path: '**/O.*/*.tap'
|
||||
if-no-files-found: ignore
|
||||
- name: Collect and show test results
|
||||
run: python .ci/cue.py test-results
|
||||
|
||||
@@ -38,7 +38,7 @@ PROJECT_NAME = pva2pva
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 1.3.1
|
||||
PROJECT_NUMBER = 1.4.2-dev
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Module (source) version
|
||||
EPICS_QSRV_MAJOR_VERSION = 1
|
||||
EPICS_QSRV_MINOR_VERSION = 3
|
||||
EPICS_QSRV_MAINTENANCE_VERSION = 1
|
||||
EPICS_QSRV_MINOR_VERSION = 4
|
||||
EPICS_QSRV_MAINTENANCE_VERSION = 2
|
||||
|
||||
# ABI version
|
||||
EPICS_QSRV_ABI_MAJOR_VERSION = 1
|
||||
@@ -9,7 +9,7 @@ EPICS_QSRV_ABI_MINOR_VERSION = 2
|
||||
|
||||
# Development flag, set to zero for release versions
|
||||
|
||||
EPICS_QSRV_DEVELOPMENT_FLAG = 0
|
||||
EPICS_QSRV_DEVELOPMENT_FLAG = 1
|
||||
|
||||
# Immediately after a release the MAINTENANCE_VERSION
|
||||
# will be incremented and the DEVELOPMENT_FLAG set to 1
|
||||
|
||||
@@ -38,7 +38,7 @@ PROJECT_NAME = pva2pva
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 1.3.1
|
||||
PROJECT_NUMBER = 1.4.2-dev
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
@@ -87,7 +87,6 @@ record(...) {
|
||||
info(Q:group, {
|
||||
"<group_name>":{
|
||||
+id:"some/NT:1.0", # top level ID
|
||||
+meta:"FLD", # map top level alarm/timeStamp
|
||||
+atomic:true, # whether monitors default to multi-locking atomicity
|
||||
"<field.name>":{
|
||||
+type:"scalar", # controls how map VAL mapped onto <field.name>
|
||||
@@ -95,7 +94,8 @@ record(...) {
|
||||
+id:"some/NT:1.0",
|
||||
+trigger:"*", # "*" or comma seperated list of <field.name>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
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -108,6 +108,7 @@ record(...) {
|
||||
@li "any"
|
||||
@li "meta"
|
||||
@li "proc"
|
||||
@li "structure"
|
||||
|
||||
The "scalar" mapping places an NTScalar or NTScalarArray as a sub-structure.
|
||||
|
||||
@@ -118,12 +119,15 @@ The "any" mapping places a variant union into which the "value" is placed.
|
||||
|
||||
The "meta" mapping ignores the "value" and places only the alarm and time
|
||||
meta-data as sub-fields.
|
||||
The special group level tag 'meta:""' allows these meta-data fields to be
|
||||
Placing an entry in a blank field name '"": {+type:"meta"}' allows these meta-data fields to be
|
||||
placed in the top-level structure.
|
||||
|
||||
The "proc" mapping uses neither "value" nor meta-data.
|
||||
Instead the target record is processed during a put.
|
||||
|
||||
The "structure" mapping allows an "+id" to be attached without a "+channel".
|
||||
So none of "+channel", "+trigger", nor "+putorder" are meaningful for a "structure" mapping.
|
||||
|
||||
@subsubsection qsrv_group_map_trig Field Update Triggers
|
||||
|
||||
The field triggers define how changes to the consitutent field
|
||||
|
||||
@@ -2,6 +2,27 @@
|
||||
|
||||
@page release_notes Release Notes
|
||||
|
||||
Release 1.4.1 (December 2023)
|
||||
==========================
|
||||
|
||||
- Bug Fixes
|
||||
- dbLoadGroup was fixed
|
||||
- Additions
|
||||
- Support for "meta" member at top of array of structs
|
||||
|
||||
Release 1.4.0 (September 2022)
|
||||
==============================
|
||||
|
||||
- Bug Fixes
|
||||
- apply ACF when writing to atomic group
|
||||
- Additions
|
||||
- Add new "structure" to @ref qsrv_group_map_types
|
||||
- Changes
|
||||
- Add Access Security hooks for single and group writes.
|
||||
- Enable "Async Soft Channel" for output links
|
||||
- When built against Base 7.0.6.1, set timeStamp.userTag from UTAG field.
|
||||
- Add DTYP="QSRV Set UTag" for longin, which sets UTAG=VAL.
|
||||
|
||||
Release 1.3.1 (June 2021)
|
||||
=========================
|
||||
|
||||
|
||||
@@ -74,3 +74,17 @@ record(mbbi, "$(N):ColorMode") {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
record(bo, "$(N):extra") {
|
||||
field(ZNAM, "foo")
|
||||
field(ONAM, "bar")
|
||||
info(Q:group, {
|
||||
"$(N):Array":{
|
||||
"attribute[1].value":{+type:"any",
|
||||
+channel:"VAL",
|
||||
+putorder:0,
|
||||
+trigger:"attribute[1].value"},
|
||||
"attribute[1]":{+type:"meta", +channel:"SEVR"}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/* demonstrate a definition equivalent to the info(Q:group, ...) tags in image.dbd */
|
||||
{
|
||||
"TST:image1:Array2":{
|
||||
"+id":"epics:nt/NTNDArray:1.0",
|
||||
"value":{"+type":"any",
|
||||
"+channel":"TST:image1:ArrayData.VAL",
|
||||
"+trigger":"*"},
|
||||
"":{"+type":"meta", "+channel":"TST:image1:ArrayData.SEVR"},
|
||||
"dimension[0].size":{"+channel":"TST:image1:ArraySize0_RBV.VAL", "+type":"plain", "+putorder":0},
|
||||
"dimension[1].size":{"+channel":"TST:image1:ArraySize1_RBV.VAL", "+type":"plain", "+putorder":0},
|
||||
"attribute[0].name":{"+type":"plain", "+channel":"TST:image1:ColorMode_.VAL"},
|
||||
"attribute[0].value":{"+type":"any", "+channel":"TST:image1:ColorMode.VAL"},
|
||||
"attribute[1].value":{"+type":"any",
|
||||
"+channel":"TST:image1:extra.VAL",
|
||||
"+putorder":0,
|
||||
"+trigger":"attribute[1].value"},
|
||||
"attribute[1]":{"+type":"meta", "+channel":"TST:image1:extra.SEVR"}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
# Example of constructing an NTEnum with a longer choices list.
|
||||
|
||||
record(longout, "$(P):ENUM:INDEX") {
|
||||
field(VAL, "1")
|
||||
field(PINI, "YES")
|
||||
info(Q:group, {
|
||||
"$(P):ENUM":{
|
||||
+id:"epics:nt/NTEnum:1.0",
|
||||
"value":{+type:"structure", +id:"enum_t"},
|
||||
"value.index":{+type:"plain", +channel:"VAL"},
|
||||
"":{+type:"meta", +channel:"VAL"}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
record(aai, "$(P):ENUM:CHOICES") {
|
||||
field(FTVL, "STRING")
|
||||
field(NELM, "64")
|
||||
field(INP , {const:["ZERO", "ONE"]})
|
||||
info(Q:group, {
|
||||
"$(P):ENUM":{
|
||||
+id:"epics:nt/NTEnum:1.0",
|
||||
"value.choices":{+type:"plain", +channel:"VAL"}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -2,5 +2,8 @@
|
||||
|
||||
dbLoadRecords("image.db","N=TST:image1")
|
||||
dbLoadRecords("table.db","N=TST:table1")
|
||||
dbLoadRecords("ntenum.db","P=TST:enum1")
|
||||
|
||||
dbLoadGroup("image.json")
|
||||
|
||||
iocInit()
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
TOP = ../..
|
||||
include $(TOP)/configure/CONFIG
|
||||
ARCH = linux-x86_64
|
||||
TARGETS = envPaths
|
||||
include $(TOP)/configure/RULES.ioc
|
||||
@@ -0,0 +1,15 @@
|
||||
HAG(MYSELF) {
|
||||
"$(USER)"
|
||||
}
|
||||
|
||||
ASG(DEFAULT) {
|
||||
RULE(1,WRITE,TRAPWRITE)
|
||||
}
|
||||
ASG(SPECIAL) {
|
||||
RULE(1,WRITE,TRAPWRITE) {
|
||||
HAG(MYSELF)
|
||||
}
|
||||
}
|
||||
ASG(RO) {
|
||||
RULE(1, READ)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
record(ao, "$(P)A") {
|
||||
info(Q:group, {
|
||||
"$(P)G": {
|
||||
"A": {+channel:"VAL", +putorder:3}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
record(ao, "$(P)B") {
|
||||
field(ASG , "SPECIAL")
|
||||
info(Q:group, {
|
||||
"$(P)G": {
|
||||
"B": {+channel:"VAL", +putorder:2}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
record(ao, "$(P)C") {
|
||||
field(ASG , "RO")
|
||||
info(Q:group, {
|
||||
"$(P)G": {
|
||||
"C": {+channel:"VAL", +putorder:1}
|
||||
}
|
||||
})
|
||||
}
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!../../bin/linux-x86_64-debug/softIocPVA
|
||||
|
||||
# Normal IOC executables will be linked against libcaputlog
|
||||
# at built time. Because pva2pva is usually built before
|
||||
# caputlog, softIocPVA can't. Instead load dynamically.
|
||||
#
|
||||
# Requires a target and configuration w/ dynamic linking support.
|
||||
#
|
||||
# registerAllRecordDeviceDrivers added in Base >= 7.0.5
|
||||
|
||||
< envPaths
|
||||
# or
|
||||
#epicsEnvSet("CAPUTLOG", "/path/to/caputlog")
|
||||
|
||||
dlload $(CAPUTLOG)/lib/$(ARCH)/libcaPutLog.so
|
||||
dbLoadDatabase $(CAPUTLOG)/dbd/caPutLog.dbd
|
||||
registerAllRecordDeviceDrivers
|
||||
|
||||
dbLoadRecords("putlog.db","P=TST:")
|
||||
|
||||
asSetFilename("$(PWD)/putlog.acf")
|
||||
asSetSubstitutions("USER=$(USER)")
|
||||
asInit
|
||||
|
||||
var caPutLogDebug 5
|
||||
caPutLogInit localhost:3456
|
||||
|
||||
iocInit()
|
||||
|
||||
|
||||
# concurrently run:
|
||||
## nc -l -p 3456
|
||||
|
||||
# Then try:
|
||||
## pvput TST:A 4
|
||||
@@ -0,0 +1,5 @@
|
||||
TOP = ../..
|
||||
include $(TOP)/configure/CONFIG
|
||||
ARCH = linux-x86_64-debug
|
||||
TARGETS = envPaths
|
||||
include $(TOP)/configure/RULES.ioc
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!../../bin/linux-x86_64-debug/softIocPVA
|
||||
|
||||
dbLoadRecords("utag.db","N=TST:")
|
||||
|
||||
iocInit()
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
record(calc, "$(N)Cnt-I") {
|
||||
field(SCAN, "1 second")
|
||||
field(CALC, "(VAL+1)%10")
|
||||
field(FLNK, "$(N)Cnt:T-I")
|
||||
}
|
||||
|
||||
record(calc, "$(N)Cnt:T-I") {
|
||||
field(INPA, "$(N)Cnt-I NPP")
|
||||
field(CALC, "A%2")
|
||||
field(FLNK, "$(N)UTag-I")
|
||||
}
|
||||
|
||||
record(longin, "$(N)UTag-I") {
|
||||
field(DTYP, "QSRV Set UTag")
|
||||
field(INP , "$(N)Cnt:T-I")
|
||||
field(FLNK, "$(N)Val-I")
|
||||
}
|
||||
|
||||
record(longin, "$(N)Val-I") {
|
||||
field(INP , "$(N)Cnt-I")
|
||||
field(TSEL, "$(N)UTag-I.TIME")
|
||||
}
|
||||
@@ -2,9 +2,11 @@
|
||||
#include <epicsMath.h>
|
||||
#include <dbAccess.h>
|
||||
#include <dbScan.h>
|
||||
#include <dbLink.h>
|
||||
#include <recGbl.h>
|
||||
#include <alarm.h>
|
||||
|
||||
#include <longinRecord.h>
|
||||
#include <waveformRecord.h>
|
||||
#include <menuFtype.h>
|
||||
|
||||
@@ -48,9 +50,48 @@ long process_spin(waveformRecord *prec)
|
||||
|
||||
prec->nord = prec->nelm;
|
||||
|
||||
#ifdef DBRutag
|
||||
prec->utag = (prec->utag+1u)&0x7fffffff;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
long process_repeat(waveformRecord *prec)
|
||||
{
|
||||
long ret;
|
||||
if(prec->ftvl!=menuFtypeULONG) {
|
||||
recGblSetSevr(prec, READ_ALARM, INVALID_ALARM);
|
||||
return S_db_badDbrtype;
|
||||
}
|
||||
|
||||
epicsUInt32 iv=0;
|
||||
if(!!(ret = dbGetLink(&prec->inp, DBF_ULONG, &iv, NULL, NULL))) {
|
||||
recGblSetSevr(prec, READ_ALARM, INVALID_ALARM);
|
||||
return ret;
|
||||
}
|
||||
|
||||
epicsUInt32 *val = (epicsUInt32*)prec->bptr;
|
||||
for(size_t i=0, N=prec->nelm; i<N; i++) {
|
||||
val[i] = iv;
|
||||
}
|
||||
|
||||
prec->nord = iv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
long process_utag(longinRecord *prec)
|
||||
{
|
||||
long status = dbGetLink(&prec->inp, DBR_LONG, &prec->val, 0, 0);
|
||||
#ifdef DBRutag
|
||||
prec->utag = prec->val;
|
||||
#else
|
||||
(void)recGblSetSevr(prec, COMM_ALARM, INVALID_ALARM);
|
||||
#endif
|
||||
return status;
|
||||
}
|
||||
|
||||
template<typename REC>
|
||||
struct dset5
|
||||
{
|
||||
@@ -63,9 +104,13 @@ struct dset5
|
||||
};
|
||||
|
||||
dset5<waveformRecord> devWfPDBDemo = {5,0,0,&init_spin,0,&process_spin};
|
||||
dset5<waveformRecord> devWfPDBDemoRepeat = {5,0,0,0,0,&process_repeat};
|
||||
dset5<longinRecord> devLoPDBUTag = {5,0,0,0,0,&process_utag};
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
epicsExportAddress(dset, devWfPDBDemo);
|
||||
epicsExportAddress(dset, devWfPDBDemoRepeat);
|
||||
epicsExportAddress(dset, devLoPDBUTag);
|
||||
}
|
||||
|
||||
+7
-4
@@ -239,7 +239,7 @@ struct PDBProcessor
|
||||
|
||||
#ifdef USE_MULTILOCK
|
||||
try {
|
||||
GroupConfig::parse(json, "", conf);
|
||||
GroupConfig::parse(json, NULL, conf);
|
||||
if(!conf.warning.empty())
|
||||
fprintf(stderr, "warning(s) from dbGroup file \"%s\"\n%s", it->c_str(), conf.warning.c_str());
|
||||
}catch(std::exception& e){
|
||||
@@ -438,6 +438,9 @@ PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_poin
|
||||
else
|
||||
builder = builder->addNestedStructure(parts[j].name);
|
||||
}
|
||||
if(parts.back().isArray()) {
|
||||
builder = builder->addNestedStructureArray(parts.back().name);
|
||||
}
|
||||
}
|
||||
|
||||
if(!mem.structID.empty())
|
||||
@@ -457,7 +460,7 @@ PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_poin
|
||||
|
||||
std::tr1::shared_ptr<PVIFBuilder> pvifbuilder(PVIFBuilder::create(mem.type, chan.chan));
|
||||
|
||||
if(!parts.empty())
|
||||
if(!parts.empty() && !parts.back().isArray())
|
||||
builder = pvifbuilder->dtype(builder, parts.back().name);
|
||||
else
|
||||
builder = pvifbuilder->dtype(builder, "");
|
||||
@@ -465,6 +468,8 @@ PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_poin
|
||||
if(!parts.empty()) {
|
||||
for(size_t j=0; j<parts.size()-1; j++)
|
||||
builder = builder->endNested();
|
||||
if(parts.back().isArray())
|
||||
builder = builder->endNested();
|
||||
}
|
||||
|
||||
if(!mem.pvname.empty()) {
|
||||
@@ -744,8 +749,6 @@ FieldName::FieldName(const std::string& pv)
|
||||
}
|
||||
if(parts.empty())
|
||||
throw std::runtime_error("Empty field name");
|
||||
if(parts.back().isArray())
|
||||
throw std::runtime_error("leaf field may not have sub-script : "+pv);
|
||||
}
|
||||
|
||||
epics::pvData::PVFieldPtr
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define PDB_H
|
||||
|
||||
#include <dbEvent.h>
|
||||
#include <asLib.h>
|
||||
|
||||
#include <pv/configuration.h>
|
||||
#include <pv/pvAccess.h>
|
||||
@@ -73,4 +74,20 @@ struct QSRV_API PDBProvider : public epics::pvAccess::ChannelProvider,
|
||||
QSRV_API
|
||||
void QSRVRegistrar_counters();
|
||||
|
||||
class AsWritePvt {
|
||||
void * pvt;
|
||||
public:
|
||||
AsWritePvt() :pvt(NULL) {}
|
||||
explicit AsWritePvt(void * pvt): pvt(pvt) {}
|
||||
~AsWritePvt() {
|
||||
asTrapWriteAfterWrite(pvt);
|
||||
}
|
||||
void swap(AsWritePvt& o) {
|
||||
std::swap(pvt, o.pvt);
|
||||
}
|
||||
private:
|
||||
AsWritePvt(const AsWritePvt&);
|
||||
AsWritePvt& operator=(const AsWritePvt&);
|
||||
};
|
||||
|
||||
#endif // PDB_H
|
||||
|
||||
+16
-2
@@ -8,6 +8,7 @@
|
||||
#include <dbAccess.h>
|
||||
#include <dbChannel.h>
|
||||
#include <dbStaticLib.h>
|
||||
#include <asLib.h>
|
||||
|
||||
#include <pv/pvAccess.h>
|
||||
#include <pv/configuration.h>
|
||||
@@ -349,12 +350,25 @@ void PDBGroupPut::put(pvd::PVStructure::shared_pointer const & value,
|
||||
// assume value may be a different struct each time... lot of wasted prep work
|
||||
const size_t npvs = channel->pv->members.size();
|
||||
std::vector<std::tr1::shared_ptr<PVIF> > putpvif(npvs);
|
||||
pvd::shared_vector<AsWritePvt> asWritePvt(npvs);
|
||||
|
||||
for(size_t i=0; i<npvs; i++)
|
||||
{
|
||||
PDBGroupPV::Info& info = channel->pv->members[i];
|
||||
if(!info.allowProc) continue;
|
||||
|
||||
AsWritePvt wrt(asTrapWriteWithData(
|
||||
channel->aspvt.at(i).aspvt,
|
||||
&channel->cred.user[0],
|
||||
&channel->cred.host[0],
|
||||
info.chan.chan,
|
||||
info.chan->final_type,
|
||||
info.chan->final_no_elements,
|
||||
NULL
|
||||
)
|
||||
);
|
||||
asWritePvt[i].swap(wrt);
|
||||
|
||||
if(!info.allowProc) continue;
|
||||
putpvif[i].reset(info.builder->attach(value, info.attachment));
|
||||
}
|
||||
|
||||
@@ -364,7 +378,7 @@ void PDBGroupPut::put(pvd::PVStructure::shared_pointer const & value,
|
||||
for(size_t i=0; ret && i<npvs; i++) {
|
||||
if(!putpvif[i].get()) continue;
|
||||
|
||||
ret |= putpvif[i]->get(*changed, doProc);
|
||||
ret |= putpvif[i]->get(*changed, doProc, channel->aspvt[i].canWrite());
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <asLib.h>
|
||||
#include <dbAccess.h>
|
||||
#include <dbChannel.h>
|
||||
#include <dbStaticLib.h>
|
||||
@@ -355,6 +356,17 @@ void PDBSinglePut::put(pvd::PVStructure::shared_pointer const & value,
|
||||
dbChannel *chan = channel->pv->chan;
|
||||
dbFldDes *fld = dbChannelFldDes(chan);
|
||||
|
||||
AsWritePvt asWritePvt (
|
||||
asTrapWriteWithData(channel->aspvt.aspvt,
|
||||
std::string(channel->cred.user.begin(), channel->cred.user.end()).c_str(),
|
||||
std::string(channel->cred.host.begin(), channel->cred.host.end()).c_str(),
|
||||
chan,
|
||||
chan->final_type,
|
||||
chan->final_no_elements,
|
||||
NULL
|
||||
)
|
||||
);
|
||||
|
||||
pvd::Status ret;
|
||||
if(!channel->aspvt.canWrite()) {
|
||||
ret = pvd::Status::error("Put not permitted");
|
||||
|
||||
+19
-2
@@ -33,6 +33,10 @@
|
||||
|
||||
#include <epicsExport.h> /* defines epicsExportSharedSymbols */
|
||||
|
||||
#if EPICS_VERSION_INT>=VERSION_INT(7,0,6,0)
|
||||
# define HAVE_SHUTDOWN_HOOKS
|
||||
#endif
|
||||
|
||||
int pvaLinkDebug;
|
||||
int pvaLinkIsolate;
|
||||
|
||||
@@ -67,6 +71,7 @@ static void shutdownStep2()
|
||||
pvaGlobal = NULL;
|
||||
}
|
||||
|
||||
#ifndef HAVE_SHUTDOWN_HOOKS
|
||||
static void stopPVAPool(void*)
|
||||
{
|
||||
try {
|
||||
@@ -84,8 +89,7 @@ static void finalizePVA(void*)
|
||||
fprintf(stderr, "Error initializing pva link handling : %s\n", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
bool atexitInstalled;
|
||||
#endif
|
||||
|
||||
/* The Initialization game...
|
||||
*
|
||||
@@ -110,10 +114,13 @@ void initPVALink(initHookState state)
|
||||
}
|
||||
pvaGlobal = new pvaGlobal_t;
|
||||
|
||||
#ifndef HAVE_SHUTDOWN_HOOKS
|
||||
static bool atexitInstalled;
|
||||
if(!atexitInstalled) {
|
||||
epicsAtExit(finalizePVA, NULL);
|
||||
atexitInstalled = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
} else if(state==initHookAfterInitDatabase) {
|
||||
pvac::ClientProvider local("server:QSRV"),
|
||||
@@ -124,7 +131,10 @@ void initPVALink(initHookState state)
|
||||
} else if(state==initHookAfterIocBuilt) {
|
||||
// after epicsExit(exitDatabase)
|
||||
// so hook registered here will be run before iocShutdown()
|
||||
|
||||
#ifndef HAVE_SHUTDOWN_HOOKS
|
||||
epicsAtExit(stopPVAPool, NULL);
|
||||
#endif
|
||||
|
||||
Guard G(pvaGlobal->lock);
|
||||
pvaGlobal->running = true;
|
||||
@@ -137,6 +147,13 @@ void initPVALink(initHookState state)
|
||||
|
||||
chan->open();
|
||||
}
|
||||
#ifdef HAVE_SHUTDOWN_HOOKS
|
||||
} else if(state==initHookAtShutdown) {
|
||||
shutdownStep1();
|
||||
|
||||
} else if(state==initHookAfterShutdown) {
|
||||
shutdownStep2();
|
||||
#endif
|
||||
}
|
||||
}catch(std::exception& e){
|
||||
cantProceed("Error initializing pva link handling : %s\n", e.what());
|
||||
|
||||
@@ -154,6 +154,8 @@ struct pvaLinkChannel : public pvac::ClientChannel::MonitorCallback,
|
||||
bool queued; // added to WorkQueue
|
||||
bool debug; // set if any jlink::debug is set
|
||||
std::tr1::shared_ptr<const void> previous_root;
|
||||
typedef std::set<dbCommon*> after_put_t;
|
||||
after_put_t after_put;
|
||||
|
||||
struct LinkSort {
|
||||
bool operator()(const pvaLink *L, const pvaLink *R) const;
|
||||
@@ -180,6 +182,12 @@ struct pvaLinkChannel : public pvac::ClientChannel::MonitorCallback,
|
||||
// pvac::ClientChanel::PutCallback
|
||||
virtual void putBuild(const epics::pvData::StructureConstPtr& build, pvac::ClientChannel::PutCallback::Args& args) OVERRIDE FINAL;
|
||||
virtual void putDone(const pvac::PutEvent& evt) OVERRIDE FINAL;
|
||||
struct AfterPut : public epicsThreadRunable {
|
||||
std::tr1::weak_ptr<pvaLinkChannel> lc;
|
||||
virtual ~AfterPut() {}
|
||||
virtual void run() OVERRIDE FINAL;
|
||||
};
|
||||
std::tr1::shared_ptr<AfterPut> AP;
|
||||
private:
|
||||
virtual void run() OVERRIDE FINAL;
|
||||
void run_dbProcess(size_t idx); // idx is index in scan_records
|
||||
@@ -198,6 +206,7 @@ struct pvaLink : public pvaLinkConfig
|
||||
static size_t num_instances;
|
||||
|
||||
bool alive; // attempt to catch some use after free
|
||||
dbfType type;
|
||||
|
||||
DBLINK * plink; // may be NULL
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ pvaLinkChannel::pvaLinkChannel(const pvaGlobal_t::channels_key_t &key, const pvd
|
||||
,queued(false)
|
||||
,debug(false)
|
||||
,links_changed(false)
|
||||
,AP(new AfterPut)
|
||||
{}
|
||||
|
||||
pvaLinkChannel::~pvaLinkChannel() {
|
||||
@@ -103,7 +104,7 @@ pvd::StructureConstPtr putRequestType = pvd::getFieldCreate()->createFieldBuilde
|
||||
void pvaLinkChannel::put(bool force)
|
||||
{
|
||||
pvd::PVStructurePtr pvReq(pvd::getPVDataCreate()->createPVStructure(putRequestType));
|
||||
pvReq->getSubFieldT<pvd::PVBoolean>("record._options.block")->put(false); // TODO: some way to expose completion...
|
||||
pvReq->getSubFieldT<pvd::PVBoolean>("record._options.block")->put(!after_put.empty());
|
||||
|
||||
unsigned reqProcess = 0;
|
||||
bool doit = force;
|
||||
@@ -191,22 +192,70 @@ void pvaLinkChannel::putBuild(const epics::pvData::StructureConstPtr& build, pva
|
||||
args.root = top;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// soo much easier with c++11 std::shared_ptr...
|
||||
struct AFLinker {
|
||||
std::tr1::shared_ptr<pvaLinkChannel> chan;
|
||||
AFLinker(const std::tr1::shared_ptr<pvaLinkChannel>& chan) :chan(chan) {}
|
||||
void operator()(pvaLinkChannel::AfterPut *) {
|
||||
chan.reset();
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void pvaLinkChannel::putDone(const pvac::PutEvent& evt)
|
||||
{
|
||||
if(evt.event==pvac::PutEvent::Fail) {
|
||||
errlogPrintf("%s PVA link put ERROR: %s\n", key.first.c_str(), evt.message.c_str());
|
||||
}
|
||||
|
||||
Guard G(lock);
|
||||
bool needscans;
|
||||
{
|
||||
Guard G(lock);
|
||||
|
||||
DEBUG(this, <<key.first<<" Put result "<<evt.event);
|
||||
DEBUG(this, <<key.first<<" Put result "<<evt.event);
|
||||
|
||||
op_put = pvac::Operation();
|
||||
needscans = !after_put.empty();
|
||||
op_put = pvac::Operation();
|
||||
|
||||
if(evt.event==pvac::PutEvent::Success) {
|
||||
// see if we need start a queue'd put
|
||||
put();
|
||||
if(evt.event==pvac::PutEvent::Success) {
|
||||
// see if we need start a queue'd put
|
||||
put();
|
||||
}
|
||||
}
|
||||
|
||||
if(needscans) {
|
||||
pvaGlobal->queue.add(AP);
|
||||
}
|
||||
}
|
||||
|
||||
void pvaLinkChannel::AfterPut::run()
|
||||
{
|
||||
std::set<dbCommon*> toscan;
|
||||
std::tr1::shared_ptr<pvaLinkChannel> link(lc.lock());
|
||||
if(!link)
|
||||
return;
|
||||
|
||||
{
|
||||
Guard G(link->lock);
|
||||
toscan.swap(link->after_put);
|
||||
}
|
||||
|
||||
for(after_put_t::iterator it=toscan.begin(), end=toscan.end();
|
||||
it!=end; ++it)
|
||||
{
|
||||
dbCommon *prec = *it;
|
||||
dbScanLock(prec);
|
||||
if(prec->pact) { // complete async. processing
|
||||
(prec)->rset->process(prec);
|
||||
|
||||
} else {
|
||||
// maybe the result of "cancellation" or some record support logic error?
|
||||
errlogPrintf("%s : not PACT when async PVA link completed. Logic error?\n", prec->name);
|
||||
}
|
||||
dbScanUnlock(prec);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void pvaLinkChannel::monitorEvent(const pvac::MonitorEvent& evt)
|
||||
@@ -335,6 +384,10 @@ void pvaLinkChannel::run()
|
||||
|
||||
if(!link->plink) continue;
|
||||
|
||||
// only scan on monitor update for input links
|
||||
if(link->type!=DBF_INLINK)
|
||||
continue;
|
||||
|
||||
// NPP and none/Default don't scan
|
||||
// PP, CP, and CPP do scan
|
||||
// PP and CPP only if SCAN=Passive
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace pvalink {
|
||||
|
||||
pvaLink::pvaLink()
|
||||
:alive(true)
|
||||
,type((dbfType)-1)
|
||||
,plink(0)
|
||||
,used_scratch(false)
|
||||
,used_queue(false)
|
||||
|
||||
+34
-3
@@ -20,10 +20,23 @@ using namespace pvalink;
|
||||
|
||||
#define CHECK_VALID() if(!self->valid()) { DEBUG(self, <<CURRENT_FUNCTION<<" "<<self->channelName<<" !valid"); return -1;}
|
||||
|
||||
dbfType getLinkType(DBLINK *plink)
|
||||
{
|
||||
dbCommon *prec = plink->precord;
|
||||
pdbRecordIterator iter(prec);
|
||||
|
||||
for(long status = dbFirstField(&iter.ent, 0); !status; status = dbNextField(&iter.ent, 0)) {
|
||||
if(iter.ent.pfield==plink)
|
||||
return iter.ent.pflddes->field_type;
|
||||
}
|
||||
throw std::logic_error("DBLINK* corrupt");
|
||||
}
|
||||
|
||||
void pvaOpenLink(DBLINK *plink)
|
||||
{
|
||||
try {
|
||||
pvaLink* self((pvaLink*)plink->value.json.jlink);
|
||||
self->type = getLinkType(plink);
|
||||
|
||||
// workaround for Base not propagating info(base:lsetDebug to us
|
||||
{
|
||||
@@ -70,6 +83,7 @@ void pvaOpenLink(DBLINK *plink)
|
||||
// open new channel
|
||||
|
||||
chan.reset(new pvaLinkChannel(key, pvRequest));
|
||||
chan->AP->lc = chan;
|
||||
pvaGlobal->channels.insert(std::make_pair(key, chan));
|
||||
doOpen = true;
|
||||
}
|
||||
@@ -397,8 +411,8 @@ pvd::ScalarType DBR2PVD(short dbr)
|
||||
throw std::invalid_argument("Unsupported DBR code");
|
||||
}
|
||||
|
||||
long pvaPutValue(DBLINK *plink, short dbrType,
|
||||
const void *pbuffer, long nRequest)
|
||||
long pvaPutValueX(DBLINK *plink, short dbrType,
|
||||
const void *pbuffer, long nRequest, bool wait)
|
||||
{
|
||||
TRY {
|
||||
(void)self;
|
||||
@@ -437,6 +451,11 @@ long pvaPutValue(DBLINK *plink, short dbrType,
|
||||
|
||||
self->used_scratch = true;
|
||||
|
||||
#ifdef USE_MULTILOCK
|
||||
if(wait)
|
||||
self->lchan->after_put.insert(plink->precord);
|
||||
#endif
|
||||
|
||||
if(!self->defer) self->lchan->put();
|
||||
|
||||
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<self->lchan->op_put.valid());
|
||||
@@ -445,6 +464,18 @@ long pvaPutValue(DBLINK *plink, short dbrType,
|
||||
return -1;
|
||||
}
|
||||
|
||||
long pvaPutValue(DBLINK *plink, short dbrType,
|
||||
const void *pbuffer, long nRequest)
|
||||
{
|
||||
return pvaPutValueX(plink, dbrType, pbuffer, nRequest, false);
|
||||
}
|
||||
|
||||
long pvaPutValueAsync(DBLINK *plink, short dbrType,
|
||||
const void *pbuffer, long nRequest)
|
||||
{
|
||||
return pvaPutValueX(plink, dbrType, pbuffer, nRequest, true);
|
||||
}
|
||||
|
||||
void pvaScanForward(DBLINK *plink)
|
||||
{
|
||||
TRY {
|
||||
@@ -485,7 +516,7 @@ lset pva_lset = {
|
||||
&pvaGetAlarm,
|
||||
&pvaGetTimeStamp,
|
||||
&pvaPutValue,
|
||||
NULL,
|
||||
&pvaPutValueAsync,
|
||||
&pvaScanForward
|
||||
//&pvaReportLink,
|
||||
};
|
||||
|
||||
+98
-14
@@ -40,19 +40,23 @@ namespace pva = epics::pvAccess;
|
||||
|
||||
DBCH::DBCH(dbChannel *ch) :chan(ch)
|
||||
{
|
||||
if(!chan)
|
||||
throw std::invalid_argument("NULL channel");
|
||||
prepare();
|
||||
}
|
||||
|
||||
DBCH::DBCH(const std::string& name)
|
||||
:chan(dbChannelCreate(name.c_str()))
|
||||
{
|
||||
if(!chan)
|
||||
throw std::invalid_argument(SB()<<"invalid channel: "<<name);
|
||||
prepare();
|
||||
}
|
||||
|
||||
void DBCH::prepare()
|
||||
{
|
||||
if(!chan)
|
||||
throw std::invalid_argument("NULL channel");
|
||||
throw std::invalid_argument(SB()<<"NULL channel");
|
||||
if(dbChannelOpen(chan)) {
|
||||
dbChannelDelete(chan);
|
||||
throw std::invalid_argument(SB()<<"Failed to open channel "<<dbChannelName(chan));
|
||||
@@ -205,16 +209,20 @@ struct pvArray : public pvCommon {
|
||||
|
||||
struct metaTIME {
|
||||
DBRstatus
|
||||
DBRamsg
|
||||
DBRtime
|
||||
DBRutag
|
||||
|
||||
enum {mask = DBR_STATUS | DBR_TIME};
|
||||
enum {mask = DBR_STATUS | DBR_AMSG | DBR_TIME | DBR_UTAG};
|
||||
};
|
||||
|
||||
struct metaDOUBLE {
|
||||
DBRstatus
|
||||
DBRamsg
|
||||
DBRunits
|
||||
DBRprecision
|
||||
DBRtime
|
||||
DBRutag
|
||||
DBRgrDouble
|
||||
DBRctrlDouble
|
||||
DBRalDouble
|
||||
@@ -222,12 +230,14 @@ struct metaDOUBLE {
|
||||
// similar junk
|
||||
DBRenumStrs
|
||||
|
||||
enum {mask = DBR_STATUS | DBR_UNITS | DBR_PRECISION | DBR_TIME | DBR_GR_DOUBLE | DBR_CTRL_DOUBLE | DBR_AL_DOUBLE};
|
||||
enum {mask = DBR_STATUS | DBR_AMSG | DBR_UNITS | DBR_PRECISION | DBR_TIME | DBR_UTAG | DBR_GR_DOUBLE | DBR_CTRL_DOUBLE | DBR_AL_DOUBLE};
|
||||
};
|
||||
|
||||
struct metaENUM {
|
||||
DBRstatus
|
||||
DBRamsg
|
||||
DBRtime
|
||||
DBRutag
|
||||
DBRenumStrs
|
||||
|
||||
// similar junk
|
||||
@@ -237,12 +247,14 @@ struct metaENUM {
|
||||
DBRctrlDouble
|
||||
DBRalDouble
|
||||
|
||||
enum {mask = DBR_STATUS | DBR_TIME | DBR_ENUM_STRS};
|
||||
enum {mask = DBR_STATUS | DBR_AMSG | DBR_TIME | DBR_UTAG | DBR_ENUM_STRS};
|
||||
};
|
||||
|
||||
struct metaSTRING {
|
||||
DBRstatus
|
||||
DBRamsg
|
||||
DBRtime
|
||||
DBRutag
|
||||
|
||||
// similar junk
|
||||
DBRenumStrs
|
||||
@@ -252,7 +264,7 @@ struct metaSTRING {
|
||||
DBRctrlDouble
|
||||
DBRalDouble
|
||||
|
||||
enum {mask = DBR_STATUS | DBR_TIME};
|
||||
enum {mask = DBR_STATUS | DBR_AMSG | DBR_TIME | DBR_UTAG};
|
||||
};
|
||||
|
||||
void attachTime(pvTimeAlarm& pvm, const pvd::PVStructurePtr& pv)
|
||||
@@ -264,6 +276,9 @@ void attachTime(pvTimeAlarm& pvm, const pvd::PVStructurePtr& pv)
|
||||
FMAP(message, PVString, "alarm.message", ALARM);
|
||||
FMAP(sec, PVLong, "timeStamp.secondsPastEpoch", ALWAYS);
|
||||
FMAP(nsec, PVInt, "timeStamp.nanoseconds", ALWAYS);
|
||||
#ifdef HAVE_UTAG
|
||||
FMAP(userTag, PVInt, "timeStamp.userTag", ALWAYS);
|
||||
#endif
|
||||
#undef FMAP
|
||||
}
|
||||
|
||||
@@ -328,16 +343,22 @@ void attachAll(PVX& pvm, const pvd::PVStructurePtr& pv)
|
||||
attachMeta(pvm, pv);
|
||||
}
|
||||
|
||||
void mapStatus(unsigned code, pvd::PVInt* status, pvd::PVString* message)
|
||||
template<typename Meta>
|
||||
void mapStatus(const Meta& meta, pvd::PVInt* status, pvd::PVString* message)
|
||||
{
|
||||
if(code<ALARM_NSTATUS)
|
||||
message->put(epicsAlarmConditionStrings[code]);
|
||||
#ifdef HAVE_UTAG
|
||||
if(meta.amsg[0]!='\0') {
|
||||
message->put(meta.amsg);
|
||||
} else
|
||||
#endif
|
||||
if(meta.status<ALARM_NSTATUS)
|
||||
message->put(epicsAlarmConditionStrings[meta.status]);
|
||||
else
|
||||
message->put("???");
|
||||
|
||||
// Arbitrary mapping from DB status codes
|
||||
unsigned out;
|
||||
switch(code) {
|
||||
switch(meta.status) {
|
||||
case NO_ALARM:
|
||||
out = 0;
|
||||
break;
|
||||
@@ -385,6 +406,10 @@ void putMetaImpl(const pvTimeAlarm& pv, const META& meta)
|
||||
if(pv.nsecMask) {
|
||||
pv.userTag->put(nsec&pv.nsecMask);
|
||||
nsec &= ~pv.nsecMask;
|
||||
#ifdef HAVE_UTAG
|
||||
} else {
|
||||
pv.userTag->put(meta.utag);
|
||||
#endif
|
||||
}
|
||||
pv.nsec->put(nsec); pv.sec->put(meta.time.secPastEpoch+POSIX_TIME_AT_EPICS_EPOCH);
|
||||
}
|
||||
@@ -400,7 +425,7 @@ void putTime(const pvTimeAlarm& pv, unsigned dbe, db_field_log *pfl)
|
||||
|
||||
putMetaImpl(pv, meta);
|
||||
if(dbe&DBE_ALARM) {
|
||||
mapStatus(meta.status, pv.status.get(), pv.message.get());
|
||||
mapStatus(meta, pv.status.get(), pv.message.get());
|
||||
pv.severity->put(meta.severity);
|
||||
}
|
||||
}
|
||||
@@ -546,7 +571,7 @@ void putMeta(const pvCommon& pv, unsigned dbe, db_field_log *pfl)
|
||||
putMetaImpl(pv, meta);
|
||||
#define FMAP(MNAME, FNAME) pv.MNAME->put(meta.FNAME)
|
||||
if(dbe&DBE_ALARM) {
|
||||
mapStatus(meta.status, pv.status.get(), pv.message.get());
|
||||
mapStatus(meta, pv.status.get(), pv.message.get());
|
||||
FMAP(severity, severity);
|
||||
}
|
||||
if(dbe&DBE_PROPERTY) {
|
||||
@@ -774,9 +799,22 @@ short PVD2DBR(pvd::ScalarType pvt)
|
||||
return -1;
|
||||
}
|
||||
|
||||
static
|
||||
pvd::StructureConstPtr buildTimeStamp()
|
||||
{
|
||||
return pvd::FieldBuilder::begin()
|
||||
->add("secondsPastEpoch", pvd::pvLong)
|
||||
->add("nanoseconds", pvd::pvInt)
|
||||
->add("userTag", pvd::pvInt)
|
||||
->createStructure();
|
||||
}
|
||||
|
||||
epics::pvData::FieldConstPtr
|
||||
ScalarBuilder::dtype()
|
||||
{
|
||||
if(!channel)
|
||||
throw std::runtime_error("+type:\"scalar\" requires +channel:");
|
||||
|
||||
short dbr = dbChannelFinalFieldType(channel);
|
||||
const long maxelem = dbChannelFinalElements(channel);
|
||||
const pvd::ScalarType pvt = DBR2PVD(dbr);
|
||||
@@ -805,7 +843,7 @@ ScalarBuilder::dtype()
|
||||
->addArray("value", pvt);
|
||||
|
||||
builder = builder->add("alarm", standard->alarm())
|
||||
->add("timeStamp", standard->timeStamp());
|
||||
->add("timeStamp", buildTimeStamp());
|
||||
|
||||
if(dbr!=DBR_ENUM) {
|
||||
builder = builder->addNestedStructure("display")
|
||||
@@ -955,6 +993,9 @@ struct PlainBuilder : public PVIFBuilder
|
||||
|
||||
// fetch the structure description
|
||||
virtual epics::pvData::FieldConstPtr dtype() OVERRIDE FINAL {
|
||||
if(!channel)
|
||||
throw std::runtime_error("+type:\"plain\" requires +channel:");
|
||||
|
||||
const short dbr = dbChannelFinalFieldType(channel);
|
||||
const long maxelem = dbChannelFinalElements(channel);
|
||||
const pvd::ScalarType pvt = DBR2PVD(dbr);
|
||||
@@ -1102,11 +1143,11 @@ struct MetaBuilder : public PVIFBuilder
|
||||
pvd::StandardFieldPtr std(pvd::getStandardField());
|
||||
if(fld.empty()) {
|
||||
return builder->add("alarm", std->alarm())
|
||||
->add("timeStamp", std->timeStamp());
|
||||
->add("timeStamp", buildTimeStamp());
|
||||
} else {
|
||||
return builder->addNestedStructure(fld)
|
||||
->add("alarm", std->alarm())
|
||||
->add("timeStamp", std->timeStamp())
|
||||
->add("timeStamp", buildTimeStamp())
|
||||
->endNested();
|
||||
}
|
||||
}
|
||||
@@ -1175,6 +1216,47 @@ struct ProcBuilder : public PVIFBuilder
|
||||
}
|
||||
};
|
||||
|
||||
struct PVIFNoOp : public PVIF
|
||||
{
|
||||
PVIFNoOp(dbChannel *channel) :PVIF(channel) {}
|
||||
|
||||
virtual void put(epics::pvData::BitSet& mask, unsigned dbe, db_field_log *pfl) OVERRIDE FINAL
|
||||
{}
|
||||
|
||||
virtual pvd::Status get(const epics::pvData::BitSet& mask, proc_t proc, bool permit) OVERRIDE FINAL
|
||||
{
|
||||
return pvd::Status();
|
||||
}
|
||||
|
||||
virtual unsigned dbe(const epics::pvData::BitSet& mask) OVERRIDE FINAL
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct IDBuilder : public PVIFBuilder
|
||||
{
|
||||
explicit IDBuilder(dbChannel* chan) :PVIFBuilder(chan) {}
|
||||
virtual ~IDBuilder() {}
|
||||
|
||||
// fetch the structure description
|
||||
virtual epics::pvData::FieldConstPtr dtype() OVERRIDE FINAL {
|
||||
throw std::logic_error("Don't call me");
|
||||
}
|
||||
|
||||
virtual epics::pvData::FieldBuilderPtr dtype(epics::pvData::FieldBuilderPtr& builder,
|
||||
const std::string& fld) OVERRIDE FINAL
|
||||
{
|
||||
// caller has already done builder->setId(...)
|
||||
return builder;
|
||||
}
|
||||
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root,
|
||||
const FieldName& fldname) OVERRIDE FINAL
|
||||
{
|
||||
return new PVIFNoOp(channel);
|
||||
}
|
||||
};
|
||||
|
||||
}//namespace
|
||||
|
||||
pvd::Status PVIF::get(const epics::pvData::BitSet& mask, proc_t proc, bool permit)
|
||||
@@ -1240,6 +1322,8 @@ PVIFBuilder* PVIFBuilder::create(const std::string& type, dbChannel* chan)
|
||||
return new MetaBuilder(chan);
|
||||
else if(type=="proc")
|
||||
return new ProcBuilder(chan);
|
||||
else if(type=="structure")
|
||||
return new IDBuilder(chan);
|
||||
else
|
||||
throw std::runtime_error(std::string("Unknown +type=")+type);
|
||||
}
|
||||
|
||||
+10
-1
@@ -30,6 +30,15 @@
|
||||
# define USE_MULTILOCK
|
||||
#endif
|
||||
|
||||
#ifndef DBRutag
|
||||
# define DBR_AMSG 0
|
||||
# define DBR_UTAG 0
|
||||
# define DBRamsg
|
||||
# define DBRutag
|
||||
#else
|
||||
# define HAVE_UTAG
|
||||
#endif
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
class ChannelRequester;
|
||||
@@ -246,7 +255,7 @@ struct LocalFL
|
||||
:pfl(pfl)
|
||||
,ours(false)
|
||||
{
|
||||
if(!pfl && (ellCount(&pchan->pre_chain)!=0 || ellCount(&pchan->pre_chain)==0)) {
|
||||
if(!pfl && (ellCount(&pchan->pre_chain)!=0 || ellCount(&pchan->post_chain)!=0)) {
|
||||
pfl = db_create_read_log(pchan);
|
||||
if(pfl) {
|
||||
ours = true;
|
||||
|
||||
@@ -7,6 +7,8 @@ link("pva", "lsetPVA")
|
||||
|
||||
# from demo.cpp
|
||||
device(waveform, CONSTANT, devWfPDBDemo, "QSRV Demo")
|
||||
device(waveform, CONSTANT, devWfPDBDemoRepeat, "QSRV Demo Replicate")
|
||||
device(longin, CONSTANT, devLoPDBUTag, "QSRV Set UTag")
|
||||
# from imagedemo.c
|
||||
function(QSRV_image_demo)
|
||||
# from pdb.cpp
|
||||
|
||||
@@ -6,6 +6,8 @@ registrar(installPVAAddLinkHook)
|
||||
|
||||
# from demo.cpp
|
||||
device(waveform, CONSTANT, devWfPDBDemo, "QSRV Demo")
|
||||
device(waveform, CONSTANT, devWfPDBDemoRepeat, "QSRV Demo Replicate")
|
||||
device(longin, CONSTANT, devLoPDBUTag, "QSRV Set UTag")
|
||||
# from imagedemo.c
|
||||
function(QSRV_image_demo)
|
||||
# from pdb.cpp
|
||||
|
||||
+2
-1
@@ -52,7 +52,8 @@ TESTPROD_HOST += testpvalink
|
||||
testpvalink_SRCS += testpvalink.cpp
|
||||
testpvalink_SRCS += pvaLinkTestIoc_registerRecordDeviceDriver.cpp
|
||||
testpvalink_LIBS += qsrv
|
||||
TESTS += testpvalink
|
||||
# too many false positive failure
|
||||
#TESTS += testpvalink
|
||||
|
||||
TESTPROD_HOST += testgroupconfig
|
||||
testgroupconfig_SRCS += testgroupconfig
|
||||
|
||||
+30
-3
@@ -144,6 +144,12 @@ void testSingleMonitor(pvac::ClientProvider& client)
|
||||
if(!mon.poll())
|
||||
testAbort("Data event w/o data");
|
||||
|
||||
#ifdef HAVE_UTAG
|
||||
testTrue(mon.changed.get(mon.root->getSubFieldT("timeStamp.userTag")->getFieldOffset()));
|
||||
mon.changed.clear(mon.root->getSubFieldT("timeStamp.userTag")->getFieldOffset());
|
||||
#else
|
||||
testSkip(1, "!HAVE_UTAG");
|
||||
#endif
|
||||
testEqual(mon.changed, pvd::BitSet()
|
||||
.set(mon.root->getSubFieldT("value")->getFieldOffset())
|
||||
.set(mon.root->getSubFieldT("alarm.severity")->getFieldOffset())
|
||||
@@ -214,6 +220,12 @@ void testGroupMonitor(pvac::ClientProvider& client)
|
||||
if(!mon.poll())
|
||||
testAbort("Data event w/o data");
|
||||
|
||||
#ifdef HAVE_UTAG
|
||||
testTrue(mon.changed.get(mon.root->getSubFieldT("fld1.timeStamp.userTag")->getFieldOffset()));
|
||||
mon.changed.clear(mon.root->getSubFieldT("fld1.timeStamp.userTag")->getFieldOffset());
|
||||
#else
|
||||
testSkip(1, "!HAVE_UTAG");
|
||||
#endif
|
||||
testEqual(mon.changed, pvd::BitSet()
|
||||
.set(mon.root->getSubFieldT("fld1.value")->getFieldOffset())
|
||||
.set(mon.root->getSubFieldT("fld1.alarm.severity")->getFieldOffset())
|
||||
@@ -224,7 +236,7 @@ void testGroupMonitor(pvac::ClientProvider& client)
|
||||
|
||||
testFieldEqual<pvd::PVDouble>(mon.root, "fld1.value", 32.0);
|
||||
#else
|
||||
testSkip(20, "No multilock");
|
||||
testSkip(21, "No multilock");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -266,6 +278,14 @@ void testGroupMonitorTriggers(pvac::ClientProvider& client)
|
||||
|
||||
testShow()<<mon.root;
|
||||
#define OFF(NAME) mon.root->getSubFieldT(NAME)->getFieldOffset()
|
||||
#ifdef HAVE_UTAG
|
||||
testTrue(mon.changed.get(OFF("fld1.timeStamp.userTag")));
|
||||
mon.changed.clear(OFF("fld1.timeStamp.userTag"));
|
||||
testTrue(mon.changed.get(OFF("fld2.timeStamp.userTag")));
|
||||
mon.changed.clear(OFF("fld2.timeStamp.userTag"));
|
||||
#else
|
||||
testSkip(2, "!HAVE_UTAG");
|
||||
#endif
|
||||
testEqual(mon.changed, pvd::BitSet()
|
||||
.set(OFF("fld1.value"))
|
||||
.set(OFF("fld1.alarm.severity"))
|
||||
@@ -288,7 +308,7 @@ void testGroupMonitorTriggers(pvac::ClientProvider& client)
|
||||
|
||||
testOk1(!mon.poll());
|
||||
#else
|
||||
testSkip(19, "No multilock");
|
||||
testSkip(21, "No multilock");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -338,7 +358,7 @@ void p2pTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
MAIN(testpdb)
|
||||
{
|
||||
testPlan(95);
|
||||
testPlan(99);
|
||||
try{
|
||||
QSRVRegistrar_counters();
|
||||
epics::RefSnapshot ref_before;
|
||||
@@ -378,6 +398,11 @@ MAIN(testpdb)
|
||||
testEqual(epics::atomic::get(PDBProvider::num_instances), 1u);
|
||||
}
|
||||
|
||||
#ifndef USE_MULTILOCK
|
||||
// test w/ 3.15 leaves ref pvac::Monitor::Impl. Probably transient.
|
||||
testTodoBegin("buggy test?");
|
||||
#endif
|
||||
|
||||
testOk1(prov.unique());
|
||||
prov.reset();
|
||||
|
||||
@@ -399,6 +424,8 @@ MAIN(testpdb)
|
||||
#endif // USE_MULTILOCK
|
||||
testEqual(epics::atomic::get(PDBSinglePV::num_instances), 0u);
|
||||
|
||||
testTodoEnd();
|
||||
|
||||
}catch(std::exception& e){
|
||||
PRINT_EXCEPTION(e);
|
||||
testAbort("Unexpected Exception: %s", e.what());
|
||||
|
||||
+27
-1
@@ -61,6 +61,31 @@ void testPut()
|
||||
testdbGetFieldEqual("src:o2.VAL", DBF_INT64, 14LL);
|
||||
}
|
||||
|
||||
void testPutAsync()
|
||||
{
|
||||
#ifdef USE_MULTILOCK
|
||||
testDiag("==== testPutAsync ====");
|
||||
|
||||
int64outRecord *trig = (int64outRecord*)testdbRecordPtr("async:trig");
|
||||
|
||||
while(!dbIsLinkConnected(&trig->out))
|
||||
testqsrvWaitForLinkEvent(&trig->out);
|
||||
|
||||
testMonitor* done = testMonitorCreate("async:after", DBE_VALUE, 0);
|
||||
|
||||
testdbPutFieldOk("async:trig.PROC", DBF_LONG, 1);
|
||||
testMonitorWait(done);
|
||||
|
||||
testdbGetFieldEqual("async:trig", DBF_LONG, 1);
|
||||
testdbGetFieldEqual("async:slow", DBF_LONG, 1); // pushed from async:trig
|
||||
testdbGetFieldEqual("async:slow2", DBF_LONG, 2);
|
||||
testdbGetFieldEqual("async:after", DBF_LONG, 3);
|
||||
|
||||
#else
|
||||
testSkip(5, "Not USE_MULTILOCK");
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C"
|
||||
@@ -68,7 +93,7 @@ void pvaLinkTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
MAIN(testpvalink)
|
||||
{
|
||||
testPlan(15);
|
||||
testPlan(20);
|
||||
|
||||
// Disable PVA client provider, use local/QSRV provider
|
||||
pvaLinkIsolate = 1;
|
||||
@@ -83,6 +108,7 @@ MAIN(testpvalink)
|
||||
IOC.init();
|
||||
testGet();
|
||||
testPut();
|
||||
testPutAsync();
|
||||
testqsrvShutdownOk();
|
||||
IOC.shutdown();
|
||||
testqsrvCleanup();
|
||||
|
||||
@@ -19,3 +19,37 @@ record(int64in, "target:i2") {
|
||||
record(int64out, "src:o2") {
|
||||
field(OUT, {"pva":"target:i2"})
|
||||
}
|
||||
|
||||
# used by testPutAsync()
|
||||
record(calc, "async:seq") {
|
||||
field(CALC, "VAL+1")
|
||||
field(VAL , "0")
|
||||
field(TPRO, "1")
|
||||
}
|
||||
|
||||
record(longout, "async:trig") {
|
||||
field(OMSL, "closed_loop")
|
||||
field(DOL , "async:seq PP")
|
||||
field(DTYP, "Async Soft Channel")
|
||||
field(OUT , { "pva":{"pv":"async:slow.A", "proc":true} })
|
||||
field(FLNK, "async:after")
|
||||
field(TPRO, "1")
|
||||
}
|
||||
|
||||
record(calcout, "async:slow") {
|
||||
field(ODLY, "1")
|
||||
field(CALC, "A")
|
||||
field(FLNK, "async:slow2")
|
||||
field(TPRO, "1")
|
||||
}
|
||||
|
||||
record(longin, "async:slow2") {
|
||||
field(INP , "async:seq PP")
|
||||
field(TPRO, "1")
|
||||
}
|
||||
|
||||
record(longin, "async:after") {
|
||||
field(INP , "async:seq PP")
|
||||
field(MDEL, "-1")
|
||||
field(TPRO, "1")
|
||||
}
|
||||
|
||||
+37
-11
@@ -72,6 +72,8 @@ void testScalar()
|
||||
testEqual(dbChannelFinalFieldType(chan_mbbi), DBR_ENUM);
|
||||
#ifdef USE_INT64
|
||||
testEqual(dbChannelFinalFieldType(chan_i64), DBR_INT64);
|
||||
#else
|
||||
testSkip(1, "!USE_INT64");
|
||||
#endif
|
||||
|
||||
pvd::PVStructurePtr root;
|
||||
@@ -136,6 +138,12 @@ void testScalar()
|
||||
dbScanUnlock((dbCommon*)prec_li);
|
||||
|
||||
#define OFF(NAME) (epicsUInt32)root->getSubFieldT(NAME)->getFieldOffset()
|
||||
#ifdef HAVE_UTAG
|
||||
testTrue(mask.get(OFF("li.timeStamp.userTag")));
|
||||
mask.clear(OFF("li.timeStamp.userTag"));
|
||||
#else
|
||||
testSkip(1, "!HAVE_UTAG");
|
||||
#endif
|
||||
testEqual(mask, pvd::BitSet()
|
||||
.set(OFF("li.value"))
|
||||
.set(OFF("li.alarm.severity"))
|
||||
@@ -168,6 +176,12 @@ void testScalar()
|
||||
dbScanUnlock((dbCommon*)prec_i64);
|
||||
|
||||
#define OFF(NAME) (epicsUInt32)root->getSubFieldT(NAME)->getFieldOffset()
|
||||
#ifdef HAVE_UTAG
|
||||
testTrue(mask.get(OFF("i64.timeStamp.userTag")));
|
||||
mask.clear(OFF("i64.timeStamp.userTag"));
|
||||
#else
|
||||
testSkip(1, "!HAVE_UTAG");
|
||||
#endif
|
||||
testEqual(mask, pvd::BitSet()
|
||||
.set(OFF("i64.value"))
|
||||
.set(OFF("i64.alarm.severity"))
|
||||
@@ -191,7 +205,10 @@ void testScalar()
|
||||
#undef OFF
|
||||
mask.clear();
|
||||
|
||||
#endif
|
||||
#else // !USE_INT64
|
||||
testSkip(2, "!USE_INT64");
|
||||
|
||||
#endif // USE_INT64
|
||||
|
||||
dbScanLock((dbCommon*)prec_si);
|
||||
prec_si->time.secPastEpoch = 0x12345678;
|
||||
@@ -200,6 +217,12 @@ void testScalar()
|
||||
dbScanUnlock((dbCommon*)prec_si);
|
||||
|
||||
#define OFF(NAME) (epicsUInt32)root->getSubFieldT(NAME)->getFieldOffset()
|
||||
#ifdef HAVE_UTAG
|
||||
testTrue(mask.get(OFF("si.timeStamp.userTag")));
|
||||
mask.clear(OFF("si.timeStamp.userTag"));
|
||||
#else
|
||||
testSkip(1, "!HAVE_UTAG");
|
||||
#endif
|
||||
testEqual(mask, pvd::BitSet()
|
||||
.set(OFF("si.value"))
|
||||
.set(OFF("si.alarm.severity"))
|
||||
@@ -207,7 +230,6 @@ void testScalar()
|
||||
.set(OFF("si.alarm.message"))
|
||||
.set(OFF("si.timeStamp.secondsPastEpoch"))
|
||||
.set(OFF("si.timeStamp.nanoseconds"))
|
||||
//.set(OFF("si.timeStamp.userTag"))
|
||||
.set(OFF("si.display.limitHigh"))
|
||||
.set(OFF("si.display.limitLow"))
|
||||
.set(OFF("si.display.description"))
|
||||
@@ -228,6 +250,14 @@ void testScalar()
|
||||
dbScanUnlock((dbCommon*)prec_ai);
|
||||
|
||||
#define OFF(NAME) (epicsUInt32)root->getSubFieldT(NAME)->getFieldOffset()
|
||||
#ifdef HAVE_UTAG
|
||||
testTrue(mask.get(OFF("ai.timeStamp.userTag")));
|
||||
mask.clear(OFF("ai.timeStamp.userTag"));
|
||||
testTrue(mask.get(OFF("ai_rval.timeStamp.userTag")));
|
||||
mask.clear(OFF("ai_rval.timeStamp.userTag"));
|
||||
#else
|
||||
testSkip(2, "!HAVE_UTAG");
|
||||
#endif
|
||||
testEqual(mask, pvd::BitSet()
|
||||
.set(OFF("ai.value"))
|
||||
.set(OFF("ai.alarm.severity"))
|
||||
@@ -235,7 +265,6 @@ void testScalar()
|
||||
.set(OFF("ai.alarm.message"))
|
||||
.set(OFF("ai.timeStamp.secondsPastEpoch"))
|
||||
.set(OFF("ai.timeStamp.nanoseconds"))
|
||||
//.set(OFF("ai.timeStamp.userTag"))
|
||||
.set(OFF("ai.display.limitHigh"))
|
||||
.set(OFF("ai.display.limitLow"))
|
||||
.set(OFF("ai.display.description"))
|
||||
@@ -254,7 +283,6 @@ void testScalar()
|
||||
.set(OFF("ai_rval.alarm.message"))
|
||||
.set(OFF("ai_rval.timeStamp.secondsPastEpoch"))
|
||||
.set(OFF("ai_rval.timeStamp.nanoseconds"))
|
||||
//.set(OFF("ai_rval.timeStamp.userTag"))
|
||||
.set(OFF("ai_rval.display.limitHigh"))
|
||||
.set(OFF("ai_rval.display.limitLow"))
|
||||
.set(OFF("ai_rval.display.description"))
|
||||
@@ -315,6 +343,8 @@ void testScalar()
|
||||
testFieldEqual<pvd::PVString>(root, "i64.display.units", "arb");
|
||||
testTodoEnd();
|
||||
testFieldEqual<pvd::PVInt>(root, "i64.display.precision", 0);
|
||||
#else
|
||||
testSkip(9, "!USE_INT64");
|
||||
#endif
|
||||
|
||||
testFieldEqual<pvd::PVString>(root, "si.value", "hello");
|
||||
@@ -380,15 +410,15 @@ void testScalar()
|
||||
pvif_i64->get(mask);
|
||||
testEqual(prec_i64->val, epicsInt64(-0x8000000000000000LL));
|
||||
dbScanUnlock((dbCommon*)prec_i64);
|
||||
#endif
|
||||
|
||||
#ifdef USE_INT64
|
||||
dbScanLock((dbCommon*)prec_i64);
|
||||
mask.clear();
|
||||
mask.set(root->getSubFieldT("i64.value")->getFieldOffset());
|
||||
pvif_i64->get(mask);
|
||||
testEqual(prec_i64->val, epicsInt64(-0x8000000000000000LL));
|
||||
dbScanUnlock((dbCommon*)prec_i64);
|
||||
#else
|
||||
testSkip(2, "!USE_INT64");
|
||||
#endif
|
||||
|
||||
dbScanLock((dbCommon*)prec_si);
|
||||
@@ -612,11 +642,7 @@ void testFilters()
|
||||
|
||||
MAIN(testpvif)
|
||||
{
|
||||
testPlan(80
|
||||
#ifdef USE_INT64
|
||||
+13
|
||||
#endif
|
||||
);
|
||||
testPlan(98);
|
||||
#ifdef USE_INT64
|
||||
testDiag("Testing of 64-bit field access");
|
||||
#else
|
||||
|
||||
Reference in New Issue
Block a user