50 Commits

Author SHA1 Message Date
Andrew Johnson cdee0b36f3 Update version numbers for release (1.4.0) 2022-09-07 11:31:34 -05:00
Michael Davidsaver db5cf25c4d update release notes 2022-07-05 11:20:26 -07:00
Michael Davidsaver 26f55cf6ce apply ACF when writing to atomic group 2022-07-05 11:11:57 -07:00
Michael Davidsaver 397a061d72 add caputlog example 2022-07-05 11:11:57 -07:00
Michael Davidsaver 3df05bf1ce use initHook shutdown hooks 2022-07-05 11:11:57 -07:00
Michael Davidsaver b9ce6cbd6e add utag demo 2022-07-05 11:10:34 -07:00
Michael Davidsaver bb85647a12 testpdb: note bug
Fails with 3.15.  Not sure why.

not ok 91 - prov.unique()
not ok 92 - epics::atomic::get(PDBProvider::num_instances) (1) == 0u (0)
ok 93 # SKIP No multilock
ok 94 # SKIP No multilock
not ok 95 - epics::atomic::get(PDBSinglePV::num_instances) (1) == 0u (0)
2022-07-05 11:08:39 -07:00
Michael Davidsaver dc22a4a4e3 fix tests wrt. userTag 2022-07-05 11:08:39 -07:00
Michael Davidsaver 5a81763fb8 Adapt to DBR_AMSG/DBR_UTAG 2022-07-05 11:08:39 -07:00
Michael Davidsaver 122e9bdbb4 enable "Async Soft Channel" for output links
Add asynchronous output link support.
When triggered, re-process record(s)
after put completes
2022-07-05 11:08:39 -07:00
Michael Davidsaver cb1b41c1b2 fixup AsWritePvt to be non-copyable
Avoids double free via asTrapWriteAfterWrite() with c++98
2022-07-05 11:08:39 -07:00
Matic Pogacnik 69a7bb3795 Add AS write hooks 2022-06-22 06:39:48 -07:00
Michael Davidsaver c95d9a68fe pvif: more error checking 2022-06-22 06:39:48 -07:00
Michael Davidsaver 8be883f98b add demo of building an NTEnum with a group 2022-06-22 06:39:48 -07:00
Michael Davidsaver b3d37e7f39 Add +type:"structure" 2022-06-22 06:39:48 -07:00
Michael Davidsaver c5cba4ccc4 ci: update 2022-06-22 06:39:48 -07:00
Michael Davidsaver f07d14b12d ci: replace ubuntu-16.04 -> docker 2021-10-21 09:53:39 -05:00
Tom Cobb 3f38e616ee Update qsrvpage.dox
Clarify use of `+meta`
2021-10-19 07:39:52 -07:00
Michael Davidsaver 61ec0715be Set next development version 2021-06-30 10:08:29 -07:00
Michael Davidsaver 466d41ebb9 1.3.1 2021-06-30 10:05:36 -07:00
Michael Davidsaver d18e2219b3 pvalink: missing NULL check 2021-04-23 15:29:45 -07:00
Michael Davidsaver ff22538129 pdb: Create db_field_log for GETs 2021-03-17 13:22:42 -07:00
Michael Davidsaver 67e668795e test PDB get w/ filter 2021-03-17 13:22:42 -07:00
Michael Davidsaver 1ac2e6c809 ci: github actions add mingw 2021-03-17 12:47:34 -07:00
Michael Davidsaver 40b327cfcb testpvif 3.15 compat 2021-03-12 11:45:13 -08:00
Michael Davidsaver 20edb8fdf5 CI update 2021-03-12 11:09:26 -08:00
Michael Davidsaver babda345b4 ci: appveyor fix mingw builds 2021-03-12 09:59:02 -08:00
Michael Davidsaver 04fbaf5a2e fix LocalFL 2021-03-12 09:59:02 -08:00
Michael Davidsaver 3438fde276 test: cover server side filters 2021-03-12 09:59:02 -08:00
Simon Rose b515fd30ca Syncing softMain.cpp with epics-base 2021-03-12 07:09:22 -08:00
Michael Davidsaver ad8b77e19f Set next development version 2021-02-26 13:26:20 -08:00
Michael Davidsaver b8389ac6a1 1.3.0 2021-02-26 13:24:26 -08:00
Andrew Johnson d47008ee53 Update GHA config
Better job names
Upload .tap files as artifacts
2021-01-05 22:12:33 -06:00
Andrew Johnson a709854f0d Update .appveyor.yml for newer ci-tools
Also set the correct notification email address
2021-01-05 22:12:33 -06:00
Andrew Johnson 527afaf856 Merge pull request #35 from anjohnson/master
About to merge my yajl5 branch to 7.0
2021-01-05 21:37:48 -06:00
Michael Davidsaver d65af720d1 doc 2020-11-28 10:45:34 -08:00
Michael Davidsaver 503cf414be 3.15 compat 2020-11-28 10:45:34 -08:00
Michael Davidsaver 1682c991d2 use dbChannel* in builder 2020-11-28 10:45:34 -08:00
Michael Davidsaver c68c0038e6 store dbChannel* in builder 2020-11-28 10:45:34 -08:00
Michael Davidsaver b69c25feb6 pass dbChannel* to PVIFBuilder::create() 2020-11-28 10:45:34 -08:00
Michael Davidsaver eae493e732 delay PVIFBuilder::create 2020-11-28 10:45:34 -08:00
Michael Davidsaver 29d00e7e38 simplify 2020-11-28 10:45:34 -08:00
Michael Davidsaver 1335d75403 add dbLoadGroup() 2020-11-28 10:45:34 -08:00
Michael Davidsaver c32cc1ba3c combine group info() processing 2020-11-28 10:44:32 -08:00
Michael Davidsaver f5278ef4ac groups: move record name prepend into parser hooks
Also, accumulate config instead of replacing
2020-11-28 10:44:32 -08:00
Michael Davidsaver b9d3ad8f90 ci: travis-ci -> github actions 2020-11-28 10:44:32 -08:00
Andrew Johnson 0e772037ee Add -v (verbose) flag to softIocPVA
Only display the startup steps with -v
Print each step *before* running it, so any error messages follow.
2020-10-28 23:28:08 -05:00
Michael Davidsaver d100eac09e update README 2020-08-30 09:29:54 -07:00
Andrew Johnson 740aad6712 Modify test database to avoid break when json5 merged
No other changes will be needed when merging the json5 branch into
Base-7.0, these changes let testpvalink pass with and without it.
2020-08-09 00:55:51 -05:00
Michael Davidsaver c66f224602 Set next development version 2020-07-26 13:43:02 -07:00
44 changed files with 1350 additions and 395 deletions
+7 -6
View File
@@ -30,6 +30,7 @@ skip_commits:
files:
- 'documentation/*'
- '**/*.md'
- '.github/**'
# Build Configurations: dll/static, regular/debug
configuration:
@@ -48,7 +49,7 @@ environment:
CMP: vs2019
BASE: 7.0
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
CMP: mingw
CMP: gcc
BASE: 7.0
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
CMP: vs2017
@@ -66,15 +67,15 @@ platform:
#---------------------------------#
build_script:
- cmd: python .ci/appveyor/do.py prepare
- cmd: python .ci/appveyor/do.py build
- cmd: python .ci/cue.py prepare
- cmd: python .ci/cue.py build
test_script:
- cmd: python .ci/appveyor/do.py test
- cmd: python .ci/cue.py test
on_finish:
- ps: Get-ChildItem *.tap -Recurse -Force | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
- cmd: python .ci/appveyor/do.py build test-results -s
- cmd: python .ci/cue.py build test-results -s
#---------------------------------#
# debugging #
@@ -96,7 +97,7 @@ notifications:
- provider: Email
to:
- me@example.com
- core-talk@aps.anl.gov
on_build_success: false
- provider: GitHubPullRequest
+1 -1
Submodule .ci updated: ecb7e43660...3db08b5977
+214
View File
@@ -0,0 +1,214 @@
# .github/workflows/ci-scripts-build.yml for use with EPICS Base ci-scripts
# (see: https://github.com/epics-base/ci-scripts)
# This is YAML - indentation levels are crucial
# Workflow name
name: Base
# Trigger on pushes and PRs to any branch
on:
push:
paths-ignore:
- .appveyor.yml
pull_request:
env:
SETUP_PATH: .ci-local:.ci
EPICS_TEST_IMPRECISE_TIMING: YES
jobs:
native:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
# 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:
- os: ubuntu-20.04
cmp: gcc
configuration: default
base: "7.0"
wine: "64"
name: "7.0 Ub-20 gcc-9 + MinGW"
- os: ubuntu-20.04
cmp: gcc
configuration: static
base: "7.0"
wine: "64"
name: "7.0 Ub-20 gcc-9 + MinGW, static"
- os: ubuntu-20.04
cmp: gcc
configuration: default
base: "3.15"
wine: "64"
name: "3.15 Ub-20 gcc-9 + MinGW"
- os: ubuntu-20.04
cmp: gcc
configuration: static
base: "7.0"
extra: "CMD_CXXFLAGS=-std=c++11"
name: "7.0 Ub-20 gcc-9 C++11, static"
- os: ubuntu-20.04
cmp: clang
configuration: default
base: "7.0"
extra: "CMD_CXXFLAGS=-std=c++11"
name: "7.0 Ub-20 clang-10 C++11"
- os: ubuntu-20.04
cmp: gcc
configuration: default
base: "7.0"
rtems: "4.10"
name: "7.0 Ub-20 gcc-9 + RT-4.10"
- os: ubuntu-20.04
cmp: gcc
configuration: default
base: "7.0"
rtems: "4.9"
name: "7.0 Ub-20 gcc-9 + RT-4.9"
- os: ubuntu-20.04
cmp: clang
configuration: default
base: "7.0"
name: "7.0 Ub-20 clang-10"
- os: macos-latest
cmp: clang
configuration: default
base: "7.0"
name: "7.0 MacOS clang-12"
- os: windows-2019
cmp: vs2019
configuration: default
base: "7.0"
name: "7.0 Win2019 MSC-19"
- os: windows-2019
cmp: vs2019
configuration: static
base: "7.0"
name: "7.0 Win2019 MSC-19, static"
- os: windows-2019
cmp: gcc
configuration: static
base: "7.0"
name: "7.0 Win2019 mingw, static"
steps:
- uses: actions/checkout@v2
with:
submodules: true
- 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: 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@v2
with:
name: tapfiles ${{ matrix.name }}
path: '**/O.*/*.tap'
- 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@v2
with:
name: tapfiles ${{ matrix.name }}
path: '**/O.*/*.tap'
- name: Collect and show test results
run: python .ci/cue.py test-results
-108
View File
@@ -1,108 +0,0 @@
# .travis.yml for use with EPICS Base ci-scripts
# (see: https://github.com/epics-base/ci-scripts)
# This is YAML - indentation levels are crucial
language: cpp
compiler: gcc
dist: bionic
cache:
directories:
- $HOME/.cache
env:
global:
- SETUP_PATH=.ci-local:.ci
addons:
apt:
packages:
# for all EPICS builds
- libreadline6-dev
- libncurses5-dev
- perl
# for clang compiler
- clang
# for mingw builds (32bit and 64bit)
- g++-mingw-w64-i686
- g++-mingw-w64-x86-64
# for RTEMS cross builds
- qemu-system-x86
install:
- ./.ci/travis/prepare.sh
script:
- ./.ci/travis/build.sh
# If you need to do more during install and build,
# add a local directory to your module and do e.g.
# - ./.ci-local/travis/install-extras.sh
# Define build jobs
# Well-known variables to use
# SET source setup file
# EXTRA content will be added to make command line
# STATIC set to YES for static build (default: NO)
# TEST set to NO to skip running the tests (default: YES)
# VV set to make build scripts verbose (default: unset)
# Usually from setup files, but may be specified or overridden
# on a job line
# MODULES list of dependency modules
# BASE branch or release tag name of the EPICS Base to use
# <MODULE> branch or release tag for a specific module
# ... see README for setup file syntax description
jobs:
include:
# Different configurations of default gcc and clang
- env: BASE=7.0
- env: BASE=7.0
compiler: clang
- env: BASE=7.0 EXTRA="CMD_CXXFLAGS=-std=c++11"
- env: BASE=7.0 EXTRA="CMD_CXXFLAGS=-std=c++11"
compiler: clang
# Trusty: compiler versions very close to RHEL 7
- env: BASE=7.0
dist: trusty
- env: BASE=7.0 EXTRA="CMD_CXXFLAGS=-std=c++11"
dist: trusty
- env: BASE=3.15 EXTRA="CMD_CXXFLAGS=-std=c++11"
dist: trusty
# Cross-compilations to Windows using MinGW and WINE
- env: BASE=7.0 WINE=32 TEST=NO STATIC=YES
compiler: mingw
- env: BASE=7.0 WINE=64 TEST=NO STATIC=NO
compiler: mingw
# Other gcc versions (added as an extra package)
- env: BASE=7.0
compiler: gcc-6
addons: { apt: { packages: ["g++-6"], sources: ["ubuntu-toolchain-r-test"] } }
- env: BASE=7.0
compiler: gcc-7
addons: { apt: { packages: ["g++-7"], sources: ["ubuntu-toolchain-r-test"] } }
# MacOS build
- env: BASE=7.0
os: osx
compiler: clang
addons: { homebrew: { packages: ["re2c"], update: true } }
+1 -1
View File
@@ -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.2.4
PROJECT_NUMBER = 1.4.0
# 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
+3
View File
@@ -21,6 +21,9 @@ p2p
A PV Access gateway (aka proxy).
The 'p2p' executable.
The P2P gateway has been deprecated in favor of
[p4p.gw](https://mdavidsaver.github.io/p4p/gw.html).
Dependencies
------------
+3 -3
View File
@@ -1,11 +1,11 @@
# Module (source) version
EPICS_QSRV_MAJOR_VERSION = 1
EPICS_QSRV_MINOR_VERSION = 2
EPICS_QSRV_MAINTENANCE_VERSION = 4
EPICS_QSRV_MINOR_VERSION = 4
EPICS_QSRV_MAINTENANCE_VERSION = 0
# ABI version
EPICS_QSRV_ABI_MAJOR_VERSION = 1
EPICS_QSRV_ABI_MINOR_VERSION = 1
EPICS_QSRV_ABI_MINOR_VERSION = 2
# Development flag, set to zero for release versions
+1 -1
View File
@@ -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.2.4
PROJECT_NUMBER = 1.4.0
# 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
+27 -4
View File
@@ -23,7 +23,9 @@ which is also the PV name.
So unlike records, the "field" of a group have a different meaning.
Group field names are _not_ part of the PV name.
A group definition is split among several records.
A group definition may be split among several records,
or included in separate JSON file(s).
For example of a group including two records is:
@code
@@ -43,6 +45,23 @@ record(ai, "rec:Y") {
}
@endcode
Or equivalently with separate .db file and .json files.
@code
# some .db
record(ai, "rec:X") {}
record(ai, "rec:Y") {}
@endcode
@code
{
"grp:name": {
"X": {+channel:"VAL"},
"Y": {+channel:"VAL"}
}
}
@endcode
This group, named "grp:name", has two fields "X" and "Y".
@code
@@ -68,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>
@@ -76,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
}
})
}
@@ -89,6 +108,7 @@ record(...) {
@li "any"
@li "meta"
@li "proc"
@li "structure"
The "scalar" mapping places an NTScalar or NTScalarArray as a sub-structure.
@@ -99,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
+29
View File
@@ -2,6 +2,35 @@
@page release_notes Release Notes
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)
=========================
- Bug Fixes
- Correct handling for server side filters.
- Changes
- Syncing softMain.cpp with epics-base
Release 1.3.0 (Feb 2021)
========================
- Changes
- Add dbLoadGroup() iocsh function to read group JSON definitions
from a file. Mappings in files must refer to full record names
instead of fields. eg. 'recname.VAL' instead of 'VAL'.
Release 1.2.4 (July 2020)
=========================
+26
View File
@@ -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"}
}
})
}
+1
View File
@@ -2,5 +2,6 @@
dbLoadRecords("image.db","N=TST:image1")
dbLoadRecords("table.db","N=TST:table1")
dbLoadRecords("ntenum.db","P=TST:enum1")
iocInit()
+5
View File
@@ -0,0 +1,5 @@
TOP = ../..
include $(TOP)/configure/CONFIG
ARCH = linux-x86_64
TARGETS = envPaths
include $(TOP)/configure/RULES.ioc
+15
View File
@@ -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)
}
+26
View File
@@ -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}
}
})
}
+35
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
TOP = ../..
include $(TOP)/configure/CONFIG
ARCH = linux-x86_64-debug
TARGETS = envPaths
include $(TOP)/configure/RULES.ioc
+5
View File
@@ -0,0 +1,5 @@
#!../../bin/linux-x86_64-debug/softIocPVA
dbLoadRecords("utag.db","N=TST:")
iocInit()
+23
View File
@@ -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")
}
+15 -6
View File
@@ -26,6 +26,7 @@ typedef std::map<std::string, pvd::AnyScalar> options_t;
typedef std::map<std::string, options_t> config_t;
struct context {
const std::string chanprefix;
std::string msg;
std::string group, field, key;
unsigned depth; // number of '{'s
@@ -34,9 +35,13 @@ struct context {
// depth 2 - Group
// depth 3 - field
context() :depth(0u) {}
context(const std::string& chanprefix, GroupConfig& conf)
:chanprefix(chanprefix)
,depth(0u)
,conf(conf)
{}
GroupConfig conf;
GroupConfig& conf;
void can_assign()
{
@@ -69,7 +74,7 @@ struct context {
fld.type = value.ref<std::string>();
} else if(key=="+channel") {
fld.channel = value.ref<std::string>();
fld.channel = chanprefix + value.ref<std::string>();
} else if(key=="+id") {
fld.id = value.ref<std::string>();
@@ -217,6 +222,7 @@ struct handler {
}// namespace
void GroupConfig::parse(const char *txt,
const char *recname,
GroupConfig& result)
{
#ifndef EPICS_YAJL_VERSION
@@ -228,7 +234,12 @@ void GroupConfig::parse(const char *txt,
std::istringstream strm(txt);
context ctxt;
std::string chanprefix;
if(recname) {
chanprefix = recname;
chanprefix += '.';
}
context ctxt(chanprefix, result);
#ifndef EPICS_YAJL_VERSION
handler handle(yajl_alloc(&conf_cbs, &conf, NULL, &ctxt));
@@ -240,6 +251,4 @@ void GroupConfig::parse(const char *txt,
if(!pvd::yajl_parse_helper(strm, handle))
throw std::runtime_error(ctxt.msg);
ctxt.conf.swap(result);
}
+19
View File
@@ -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,24 @@ long process_spin(waveformRecord *prec)
prec->nord = prec->nelm;
#ifdef DBRutag
prec->utag = (prec->utag+1u)&0x7fffffff;
#endif
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 +80,11 @@ struct dset5
};
dset5<waveformRecord> devWfPDBDemo = {5,0,0,&init_spin,0,&process_spin};
dset5<longinRecord> devLoPDBUTag = {5,0,0,0,0,&process_utag};
} // namespace
extern "C" {
epicsExportAddress(dset, devWfPDBDemo);
epicsExportAddress(dset, devLoPDBUTag);
}
+154 -102
View File
@@ -59,16 +59,14 @@ struct Splitter {
};
struct GroupMemberInfo {
// consumes builder
GroupMemberInfo(const std::string& a, const std::string& b, const std::tr1::shared_ptr<PVIFBuilder>& builder)
:pvname(a), pvfldname(b), builder(builder), putorder(0) {}
GroupMemberInfo() :putorder(0) {}
std::string pvname, // aka. name passed to dbChannelOpen()
pvfldname; // PVStructure sub-field
std::string structID; // ID to assign to sub-field
std::string type; // mapping type
typedef std::set<std::string> triggers_t;
triggers_t triggers; // names in GroupInfo::members_names which are post()d on events from pvfldname
std::tr1::shared_ptr<PVIFBuilder> builder; // not actually shared, but allows us to be copyable
int putorder;
bool operator<(const GroupMemberInfo& o) const {
@@ -100,9 +98,6 @@ struct PDBProcessor
typedef std::map<std::string, GroupInfo> groups_t;
groups_t groups;
std::string recbase;
GroupInfo *curgroup;
// validate trigger mappings and process into bit map form
void resolveTriggers()
{
@@ -177,8 +172,13 @@ struct PDBProcessor
}
}
PDBProcessor() : curgroup(NULL)
PDBProcessor()
{
#ifdef USE_MULTILOCK
GroupConfig conf;
#endif
// process info(Q:Group, ...)
for(pdbRecordIterator rec; !rec.done(); rec.next())
{
const char *json = rec.info("Q:group");
@@ -189,107 +189,154 @@ struct PDBProcessor
warned = true;
fprintf(stderr, "%s: ignoring info(Q:Group, ...\n", rec.name());
}
#else
#endif
if(PDBProviderDebug>2) {
fprintf(stderr, "%s: info(Q:Group, ...\n", rec.name());
}
#ifdef USE_MULTILOCK
try {
GroupConfig conf;
GroupConfig::parse(json, conf);
GroupConfig::parse(json, rec.name(), conf);
if(!conf.warning.empty())
fprintf(stderr, "%s: warning(s) from info(Q:group, ...\n%s", rec.name(), conf.warning.c_str());
recbase = rec.name();
recbase += ".";
for(GroupConfig::groups_t::const_iterator git=conf.groups.begin(), gend=conf.groups.end();
git!=gend; ++git)
{
const std::string& grpname = git->first;
const GroupConfig::Group& grp = git->second;
if(dbChannelTest(grpname.c_str())==0) {
fprintf(stderr, "%s : Error: Group name conflicts with record name. Ignoring...\n", grpname.c_str());
continue;
}
groups_t::iterator it = groups.find(grpname);
if(it==groups.end()) {
// lazy creation of group
std::pair<groups_t::iterator, bool> ins(groups.insert(std::make_pair(grpname, GroupInfo(grpname))));
it = ins.first;
}
curgroup = &it->second;
if(!grp.id.empty())
curgroup->structID = grp.id;
for(GroupConfig::Group::fields_t::const_iterator fit=grp.fields.begin(), fend=grp.fields.end();
fit!=fend; ++fit)
{
const std::string& fldname = fit->first;
const GroupConfig::Field& fld = fit->second;
GroupInfo::members_map_t::const_iterator oldgrp(curgroup->members_map.find(fldname));
if(oldgrp!=curgroup->members_map.end()) {
fprintf(stderr, "%s.%s Warning: ignoring duplicate mapping %s%s\n",
grpname.c_str(), fldname.c_str(),
recbase.c_str(), fld.channel.c_str());
continue;
}
std::tr1::shared_ptr<PVIFBuilder> builder(PVIFBuilder::create(fld.type));
curgroup->members.push_back(GroupMemberInfo(fld.channel.empty() ? fld.channel : recbase + fld.channel, fldname, builder));
curgroup->members.back().structID = fld.id;
curgroup->members.back().putorder = fld.putorder;
curgroup->members_map[fldname] = (size_t)-1; // placeholder see below
if(PDBProviderDebug>2) {
fprintf(stderr, " pdb map '%s.%s' <-> '%s'\n",
curgroup->name.c_str(),
curgroup->members.back().pvfldname.c_str(),
curgroup->members.back().pvname.c_str());
}
if(!fld.trigger.empty()) {
GroupInfo::triggers_t::iterator it = curgroup->triggers.find(fldname);
if(it==curgroup->triggers.end()) {
std::pair<GroupInfo::triggers_t::iterator, bool> ins(curgroup->triggers.insert(
std::make_pair(fldname, GroupInfo::triggers_set_t())));
it = ins.first;
}
Splitter sep(fld.trigger.c_str(), ',');
std::string target;
while(sep.snip(target)) {
curgroup->hastriggers = true;
it->second.insert(target);
}
}
}
if(grp.atomic_set) {
GroupInfo::tribool V = grp.atomic ? GroupInfo::True : GroupInfo::False;
if(curgroup->atomic!=GroupInfo::Unset && curgroup->atomic!=V)
fprintf(stderr, "%s Warning: pdb atomic setting inconsistent '%s'\n",
grpname.c_str(), curgroup->name.c_str());
curgroup->atomic=V;
if(PDBProviderDebug>2)
fprintf(stderr, " pdb atomic '%s' %s\n",
curgroup->name.c_str(), curgroup->atomic ? "YES" : "NO");
}
}
}catch(std::exception& e){
fprintf(stderr, "%s: Error parsing info(\"Q:group\", ... : %s\n",
rec.record()->name, e.what());
}
#endif // USE_MULTILOCK
#endif
}
// process group definition files
for(PDBProvider::group_files_t::const_iterator it(PDBProvider::group_files.begin()), end(PDBProvider::group_files.end());
it != end; ++it)
{
std::ifstream jfile(it->c_str());
if(!jfile.is_open()) {
fprintf(stderr, "Error opening \"%s\"\n", it->c_str());
continue;
}
std::vector<char> contents;
size_t pos=0u;
while(true) {
contents.resize(pos+1024u);
if(!jfile.read(&contents[pos], contents.size()-pos))
break;
pos += jfile.gcount();
}
if(jfile.bad() || !jfile.eof()) {
fprintf(stderr, "Error reading \"%s\"\n", it->c_str());
continue;
}
contents.push_back('\0');
const char *json = &contents[0];
if(PDBProviderDebug>2) {
fprintf(stderr, "Process dbGroup file \"%s\"\n", it->c_str());
}
#ifdef USE_MULTILOCK
try {
GroupConfig::parse(json, "", 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){
fprintf(stderr, "Error from dbGroup file \"%s\"\n%s", it->c_str(), e.what());
}
#endif
}
#ifdef USE_MULTILOCK
for(GroupConfig::groups_t::const_iterator git=conf.groups.begin(), gend=conf.groups.end();
git!=gend; ++git)
{
const std::string& grpname = git->first;
const GroupConfig::Group& grp = git->second;
try {
if(dbChannelTest(grpname.c_str())==0) {
fprintf(stderr, "%s : Error: Group name conflicts with record name. Ignoring...\n", grpname.c_str());
continue;
}
groups_t::iterator it = groups.find(grpname);
if(it==groups.end()) {
// lazy creation of group
std::pair<groups_t::iterator, bool> ins(groups.insert(std::make_pair(grpname, GroupInfo(grpname))));
it = ins.first;
}
GroupInfo *curgroup = &it->second;
if(!grp.id.empty())
curgroup->structID = grp.id;
for(GroupConfig::Group::fields_t::const_iterator fit=grp.fields.begin(), fend=grp.fields.end();
fit!=fend; ++fit)
{
const std::string& fldname = fit->first;
const GroupConfig::Field& fld = fit->second;
if(curgroup->members_map.find(fldname) != curgroup->members_map.end()) {
fprintf(stderr, "%s.%s Warning: ignoring duplicate mapping %s\n",
grpname.c_str(), fldname.c_str(),
fld.channel.c_str());
continue;
}
curgroup->members.push_back(GroupMemberInfo());
GroupMemberInfo& info = curgroup->members.back();
info.pvname = fld.channel;
info.pvfldname = fldname;
info.structID = fld.id;
info.putorder = fld.putorder;
info.type = fld.type;
curgroup->members_map[fldname] = (size_t)-1; // placeholder see below
if(PDBProviderDebug>2) {
fprintf(stderr, " pdb map '%s.%s' <-> '%s'\n",
curgroup->name.c_str(),
curgroup->members.back().pvfldname.c_str(),
curgroup->members.back().pvname.c_str());
}
if(!fld.trigger.empty()) {
GroupInfo::triggers_t::iterator it = curgroup->triggers.find(fldname);
if(it==curgroup->triggers.end()) {
std::pair<GroupInfo::triggers_t::iterator, bool> ins(curgroup->triggers.insert(
std::make_pair(fldname, GroupInfo::triggers_set_t())));
it = ins.first;
}
Splitter sep(fld.trigger.c_str(), ',');
std::string target;
while(sep.snip(target)) {
curgroup->hastriggers = true;
it->second.insert(target);
}
}
}
if(grp.atomic_set) {
GroupInfo::tribool V = grp.atomic ? GroupInfo::True : GroupInfo::False;
if(curgroup->atomic!=GroupInfo::Unset && curgroup->atomic!=V)
fprintf(stderr, "%s Warning: pdb atomic setting inconsistent '%s'\n",
grpname.c_str(), curgroup->name.c_str());
curgroup->atomic=V;
if(PDBProviderDebug>2)
fprintf(stderr, " pdb atomic '%s' %s\n",
curgroup->name.c_str(), curgroup->atomic ? "YES" : "NO");
}
}catch(std::exception& e){
fprintf(stderr, "Error processing Q:group \"%s\" : %s\n",
grpname.c_str(), e.what());
}
}
// re-sort GroupInfo::members to ensure the shorter names appear first
@@ -311,6 +358,7 @@ struct PDBProcessor
resolveTriggers();
// must not re-sort members after this point as resolveTriggers()
// has stored array indicies.
#endif
}
};
@@ -318,6 +366,8 @@ struct PDBProcessor
size_t PDBProvider::num_instances;
std::list<std::string> PDBProvider::group_files;
PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_pointer &)
{
/* Long view
@@ -405,10 +455,12 @@ PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_poin
chan.swap(temp);
}
std::tr1::shared_ptr<PVIFBuilder> pvifbuilder(PVIFBuilder::create(mem.type, chan.chan));
if(!parts.empty())
builder = mem.builder->dtype(builder, parts.back().name, chan);
builder = pvifbuilder->dtype(builder, parts.back().name);
else
builder = mem.builder->dtype(builder, "", chan);
builder = pvifbuilder->dtype(builder, "");
if(!parts.empty()) {
for(size_t j=0; j<parts.size()-1; j++)
@@ -426,7 +478,7 @@ PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_poin
}
info.allowProc = mem.putorder != std::numeric_limits<int>::min();
info.builder = PTRMOVE(mem.builder);
info.builder = PTRMOVE(pvifbuilder);
assert(info.builder.get());
info.attachment.swap(parts);
@@ -517,7 +569,7 @@ PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_poin
info.evt_VALUE.self = info.evt_PROPERTY.self = pv;
assert(info.chan);
info.pvif.reset(info.builder->attach(info.chan, pv->complete, info.attachment));
info.pvif.reset(info.builder->attach(pv->complete, info.attachment));
// TODO: don't need evt_PROPERTY for PVIF plain
dbChannel *pchan = info.chan2.chan ? info.chan2.chan : info.chan.chan;
+20
View File
@@ -2,6 +2,7 @@
#define PDB_H
#include <dbEvent.h>
#include <asLib.h>
#include <pv/configuration.h>
#include <pv/pvAccess.h>
@@ -64,10 +65,29 @@ struct QSRV_API PDBProvider : public epics::pvAccess::ChannelProvider,
dbEventCtx event_context;
typedef std::list<std::string> group_files_t;
static group_files_t group_files;
static size_t num_instances;
};
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
+24 -7
View File
@@ -8,6 +8,7 @@
#include <dbAccess.h>
#include <dbChannel.h>
#include <dbStaticLib.h>
#include <asLib.h>
#include <pv/pvAccess.h>
#include <pv/configuration.h>
@@ -334,7 +335,7 @@ PDBGroupPut::PDBGroupPut(const PDBGroupChannel::shared_pointer& channel,
{
PDBGroupPV::Info& info = channel->pv->members[i];
pvif[i].reset(info.builder->attach(info.chan, pvf, info.attachment));
pvif[i].reset(info.builder->attach(pvf, info.attachment));
}
}
@@ -349,13 +350,26 @@ 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;
putpvif[i].reset(info.builder->attach(info.chan, value, info.attachment));
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));
}
pvd::Status ret;
@@ -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 {
@@ -394,8 +408,10 @@ void PDBGroupPut::get()
changed->clear();
if(atomic) {
DBManyLocker L(channel->pv->locker);
for(size_t i=0; i<npvs; i++)
pvif[i]->put(*changed, DBE_VALUE|DBE_ALARM|DBE_PROPERTY, NULL);
for(size_t i=0; i<npvs; i++) {
LocalFL FL(NULL, channel->pv->members[i].chan);
pvif[i]->put(*changed, DBE_VALUE|DBE_ALARM|DBE_PROPERTY, FL.pfl);
}
} else {
for(size_t i=0; i<npvs; i++)
@@ -403,7 +419,8 @@ void PDBGroupPut::get()
PDBGroupPV::Info& info = channel->pv->members[i];
DBScanLocker L(dbChannelRecord(info.chan));
pvif[i]->put(*changed, DBE_VALUE|DBE_ALARM|DBE_PROPERTY, NULL);
LocalFL FL(NULL, info.chan);
pvif[i]->put(*changed, DBE_VALUE|DBE_ALARM|DBE_PROPERTY, FL.pfl);
}
}
//TODO: report unused fields as changed?
+1 -1
View File
@@ -59,7 +59,7 @@ struct QSRV_API GroupConfig
std::swap(warning, o.warning);
}
static void parse(const char *txt,
static void parse(const char *txt, const char *recname,
GroupConfig& result);
};
+20 -7
View File
@@ -2,6 +2,7 @@
#include <string.h>
#include <asLib.h>
#include <dbAccess.h>
#include <dbChannel.h>
#include <dbStaticLib.h>
@@ -96,7 +97,7 @@ void pdb_single_event(void *user_arg, struct dbChannel *chan,
PDBSinglePV::PDBSinglePV(DBCH& chan,
const PDBProvider::shared_pointer& prov)
:provider(prov)
,builder(new ScalarBuilder)
,builder(new ScalarBuilder(chan.chan))
,interested_iterating(false)
,evt_VALUE(this)
,evt_PROPERTY(this)
@@ -108,11 +109,11 @@ PDBSinglePV::PDBSinglePV(DBCH& chan,
this->chan2.swap(temp);
}
this->chan.swap(chan);
fielddesc = std::tr1::static_pointer_cast<const pvd::Structure>(builder->dtype(this->chan));
fielddesc = std::tr1::static_pointer_cast<const pvd::Structure>(builder->dtype());
complete = pvd::getPVDataCreate()->createPVStructure(fielddesc);
FieldName temp;
pvif.reset(builder->attach(this->chan, complete, temp));
pvif.reset(builder->attach(complete, temp));
epics::atomic::increment(num_instances);
}
@@ -308,7 +309,7 @@ PDBSinglePut::PDBSinglePut(const PDBSingleChannel::shared_pointer &channel,
,requester(requester)
,changed(new pvd::BitSet(channel->fielddesc->getNumberFields()))
,pvf(pvd::getPVDataCreate()->createPVStructure(channel->fielddesc))
,pvif(channel->pv->builder->attach(channel->pv->chan, pvf, FieldName()))
,pvif(channel->pv->builder->attach(pvf, FieldName()))
,notifyBusy(0)
,doProc(PVIF::ProcPassive)
,doWait(false)
@@ -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");
@@ -375,7 +387,7 @@ void PDBSinglePut::put(pvd::PVStructure::shared_pointer const & value,
// TODO: dbNotify doesn't allow us for force processing
// assume value may be a different struct each time
p2p::auto_ptr<PVIF> putpvif(channel->pv->builder->attach(channel->pv->chan, value, FieldName()));
p2p::auto_ptr<PVIF> putpvif(channel->pv->builder->attach(value, FieldName()));
unsigned mask = putpvif->dbe(*changed);
if(mask!=DBE_VALUE) {
@@ -397,7 +409,7 @@ void PDBSinglePut::put(pvd::PVStructure::shared_pointer const & value,
return; // skip notification
} else {
// assume value may be a different struct each time
p2p::auto_ptr<PVIF> putpvif(channel->pv->builder->attach(channel->pv->chan, value, FieldName()));
p2p::auto_ptr<PVIF> putpvif(channel->pv->builder->attach(value, FieldName()));
try{
DBScanLocker L(chan);
ret = putpvif->get(*changed, doProc);
@@ -426,7 +438,8 @@ void PDBSinglePut::get()
changed->clear();
{
DBScanLocker L(pvif->chan);
pvif->put(*changed, DBE_VALUE|DBE_ALARM|DBE_PROPERTY, NULL);
LocalFL FL(NULL, pvif->chan);
pvif->put(*changed, DBE_VALUE|DBE_ALARM|DBE_PROPERTY, FL.pfl);
}
//TODO: report unused fields as changed?
changed->clear();
+3
View File
@@ -49,6 +49,9 @@ QSRV_API unsigned qsrvVersion(void);
/** returns QSRV_ABI_VERSION_INT captured at compilation time */
QSRV_API unsigned qsrvABIVersion(void);
QSRV_API
long dbLoadGroup(const char* fname);
QSRV_API void testqsrvWaitForLinkEvent(struct link *plink);
/** Call before testIocShutdownOk()
+19 -2
View File
@@ -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());
+9
View File
@@ -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
+60 -7
View File
@@ -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
+4 -1
View File
@@ -7,6 +7,7 @@ namespace pvalink {
pvaLink::pvaLink()
:alive(true)
,type((dbfType)-1)
,plink(0)
,used_scratch(false)
,used_queue(false)
@@ -89,7 +90,9 @@ pvd::PVField::const_shared_pointer pvaLink::getSubField(const char *name)
} else {
// we access a sub-struct
ret = lchan->op_mon.root->getSubField(fieldName);
if(ret->getField()->getType()!=pvd::structure) {
if(!ret) {
// noop
} else if(ret->getField()->getType()!=pvd::structure) {
// addressed sub-field isn't a sub-structure
if(strcmp(name, "value")!=0) {
// unless we are trying to fetch the "value", we fail here
+34 -3
View File
@@ -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,
};
+121 -42
View File
@@ -205,16 +205,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 +226,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 +243,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 +260,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 +272,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 +339,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 +402,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 +421,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 +567,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 +795,22 @@ short PVD2DBR(pvd::ScalarType pvt)
return -1;
}
epics::pvData::FieldConstPtr
ScalarBuilder::dtype(dbChannel *channel)
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 +839,7 @@ ScalarBuilder::dtype(dbChannel *channel)
->addArray("value", pvt);
builder = builder->add("alarm", standard->alarm())
->add("timeStamp", standard->timeStamp());
->add("timeStamp", buildTimeStamp());
if(dbr!=DBR_ENUM) {
builder = builder->addNestedStructure("display")
@@ -830,7 +864,7 @@ ScalarBuilder::dtype(dbChannel *channel)
}
PVIF*
ScalarBuilder::attach(dbChannel *channel, const epics::pvData::PVStructurePtr& root, const FieldName& fldname)
ScalarBuilder::attach(const epics::pvData::PVStructurePtr& root, const FieldName& fldname)
{
if(!channel)
throw std::runtime_error("+type:\"scalar\" requires +channel:");
@@ -950,10 +984,14 @@ struct PVIFPlain : public PVIF
struct PlainBuilder : public PVIFBuilder
{
explicit PlainBuilder(dbChannel* chan) :PVIFBuilder(chan) {}
virtual ~PlainBuilder() {}
// fetch the structure description
virtual epics::pvData::FieldConstPtr dtype(dbChannel *channel) OVERRIDE FINAL {
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);
@@ -970,8 +1008,7 @@ struct PlainBuilder : public PVIFBuilder
// Attach to a structure instance.
// must be of the type returned by dtype().
// need not be the root structure
virtual PVIF* attach(dbChannel *channel,
const epics::pvData::PVStructurePtr& root,
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root,
const FieldName& fldname) OVERRIDE FINAL
{
if(!channel)
@@ -990,10 +1027,11 @@ struct PlainBuilder : public PVIFBuilder
struct AnyScalarBuilder : public PVIFBuilder
{
explicit AnyScalarBuilder(dbChannel* chan) :PVIFBuilder(chan) {}
virtual ~AnyScalarBuilder() {}
// fetch the structure description
virtual epics::pvData::FieldConstPtr dtype(dbChannel *channel) OVERRIDE FINAL {
virtual epics::pvData::FieldConstPtr dtype() OVERRIDE FINAL {
(void)channel; //ignored
return pvd::getFieldCreate()->createVariantUnion();
}
@@ -1001,8 +1039,7 @@ struct AnyScalarBuilder : public PVIFBuilder
// Attach to a structure instance.
// must be of the type returned by dtype().
// need not be the root structure
virtual PVIF* attach(dbChannel *channel,
const epics::pvData::PVStructurePtr& root,
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root,
const FieldName& fldname) OVERRIDE FINAL
{
if(!channel)
@@ -1088,25 +1125,25 @@ struct PVIFMeta : public PVIF
struct MetaBuilder : public PVIFBuilder
{
explicit MetaBuilder(dbChannel* chan) :PVIFBuilder(chan) {}
virtual ~MetaBuilder() {}
// fetch the structure description
virtual epics::pvData::FieldConstPtr dtype(dbChannel *channel) OVERRIDE FINAL {
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,
dbChannel *channel)
const std::string& fld) OVERRIDE FINAL
{
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();
}
}
@@ -1114,8 +1151,7 @@ struct MetaBuilder : public PVIFBuilder
// Attach to a structure instance.
// must be of the type returned by dtype().
// need not be the root structure
virtual PVIF* attach(dbChannel *channel,
const epics::pvData::PVStructurePtr& root,
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root,
const FieldName& fldname) OVERRIDE FINAL
{
if(!channel)
@@ -1152,20 +1188,21 @@ struct PVIFProc : public PVIF
struct ProcBuilder : public PVIFBuilder
{
explicit ProcBuilder(dbChannel* chan) :PVIFBuilder(chan) {}
virtual ~ProcBuilder() {}
// fetch the structure description
virtual epics::pvData::FieldConstPtr dtype(dbChannel *channel) OVERRIDE FINAL {
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,
dbChannel *channel) OVERRIDE FINAL
const std::string& fld) OVERRIDE FINAL
{
// invisible
return builder;
}
virtual PVIF* attach(dbChannel *channel,
const epics::pvData::PVStructurePtr& root,
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root,
const FieldName& fldname) OVERRIDE FINAL
{
if(!channel)
@@ -1175,6 +1212,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)
@@ -1216,31 +1294,32 @@ pvd::Status PVIF::get(const epics::pvData::BitSet& mask, proc_t proc, bool permi
epics::pvData::FieldBuilderPtr
PVIFBuilder::dtype(epics::pvData::FieldBuilderPtr& builder,
const std::string &fld,
dbChannel *channel)
const std::string &fld)
{
if(fld.empty())
throw std::runtime_error("Can't attach this +type to root");
throw std::runtime_error(SB()<<"Can't attach +type "<<typeid(*this).name()<<" to root");
epics::pvData::FieldConstPtr ftype(this->dtype(channel));
epics::pvData::FieldConstPtr ftype(this->dtype());
if(ftype)
builder = builder->add(fld, ftype);
return builder;
}
PVIFBuilder* PVIFBuilder::create(const std::string& type)
PVIFBuilder* PVIFBuilder::create(const std::string& type, dbChannel* chan)
{
if(type.empty() || type=="scalar")
return new ScalarBuilder;
return new ScalarBuilder(chan);
else if(type=="plain")
return new PlainBuilder;
return new PlainBuilder(chan);
else if(type=="any")
return new AnyScalarBuilder;
return new AnyScalarBuilder(chan);
else if(type=="meta")
return new MetaBuilder;
return new MetaBuilder(chan);
else if(type=="proc")
return new ProcBuilder;
return new ProcBuilder(chan);
else if(type=="structure")
return new IDBuilder(chan);
else
throw std::runtime_error(std::string("Unknown +type=")+type);
}
+21 -9
View File
@@ -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;
@@ -254,6 +263,7 @@ struct LocalFL
if(pfl) pfl = dbChannelRunPostChain(pchan, pfl);
}
}
this->pfl = pfl;
}
~LocalFL() {
if(ours) db_delete_field_log(pfl);
@@ -390,26 +400,27 @@ private:
* Caller than creates a PVStructure and uses PVIFBuilder::attach() to
* build mappings for each dbChannel in the composed locations.
*/
struct QSRV_API PVIFBuilder {
struct QSRV_API PVIFBuilder
{
dbChannel* const channel;
virtual ~PVIFBuilder() {}
// fetch the structure description
virtual epics::pvData::FieldConstPtr dtype(dbChannel *channel) =0;
virtual epics::pvData::FieldConstPtr dtype() =0;
virtual epics::pvData::FieldBuilderPtr dtype(epics::pvData::FieldBuilderPtr& builder,
const std::string& fld,
dbChannel *channel);
const std::string& fld);
// Attach to a structure instance.
// must be of the type returned by dtype().
// must be the root structure
virtual PVIF* attach(dbChannel *channel, const epics::pvData::PVStructurePtr& root, const FieldName& fld) =0;
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root, const FieldName& fld) =0;
// entry point for Builder
static PVIFBuilder* create(const std::string& name);
static PVIFBuilder* create(const std::string& mapname, dbChannel* chan);
protected:
PVIFBuilder() {}
explicit PVIFBuilder(dbChannel* chan) : channel(chan) {}
private:
PVIFBuilder(const PVIFBuilder&);
PVIFBuilder& operator=(const PVIFBuilder&);
@@ -417,10 +428,11 @@ private:
struct QSRV_API ScalarBuilder : public PVIFBuilder
{
explicit ScalarBuilder(dbChannel* chan) :PVIFBuilder(chan) {}
virtual ~ScalarBuilder() {}
virtual epics::pvData::FieldConstPtr dtype(dbChannel *channel) OVERRIDE FINAL;
virtual PVIF* attach(dbChannel *channel, const epics::pvData::PVStructurePtr& root, const FieldName& fld) OVERRIDE FINAL;
virtual epics::pvData::FieldConstPtr dtype() OVERRIDE FINAL;
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root, const FieldName& fld) OVERRIDE FINAL;
};
+1
View File
@@ -7,6 +7,7 @@ link("pva", "lsetPVA")
# from demo.cpp
device(waveform, CONSTANT, devWfPDBDemo, "QSRV Demo")
device(longin, CONSTANT, devLoPDBUTag, "QSRV Set UTag")
# from imagedemo.c
function(QSRV_image_demo)
# from pdb.cpp
+1
View File
@@ -6,6 +6,7 @@ registrar(installPVAAddLinkHook)
# from demo.cpp
device(waveform, CONSTANT, devWfPDBDemo, "QSRV Demo")
device(longin, CONSTANT, devLoPDBUTag, "QSRV Set UTag")
# from imagedemo.c
function(QSRV_image_demo)
# from pdb.cpp
+42
View File
@@ -46,8 +46,49 @@ void QSRVRegistrar_counters()
epics::registerRefCounter("PDBProvider", &PDBProvider::num_instances);
}
long dbLoadGroup(const char* fname)
{
try {
if(!fname) {
printf("dbLoadGroup(\"file.json\")\n"
"\n"
"Load additional DB group definitions from file.\n");
return 1;
}
#ifndef USE_MULTILOCK
static bool warned;
if(!warned) {
warned = true;
fprintf(stderr, "ignoring %s\n", fname);
}
#endif
if(fname[0]=='-') {
fname++;
if(fname[0]=='*' && fname[1]=='\0') {
PDBProvider::group_files.clear();
} else {
PDBProvider::group_files.remove(fname);
}
} else {
PDBProvider::group_files.remove(fname);
PDBProvider::group_files.push_back(fname);
}
return 0;
}catch(std::exception& e){
fprintf(stderr, "Error: %s\n", e.what());
return 1;
}
}
namespace {
void dbLoadGroupWrap(const char* fname)
{
(void)dbLoadGroup(fname);
}
void dbgl(int lvl, const char *pattern)
{
if(!pattern)
@@ -88,6 +129,7 @@ void QSRVRegistrar()
QSRVRegistrar_counters();
pva::ChannelProviderRegistry::servers()->addSingleton<PDBProvider>("QSRV");
epics::iocshRegister<int, const char*, &dbgl>("dbgl", "level", "pattern");
epics::iocshRegister<const char*, &dbLoadGroupWrap>("dbLoadGroup", "jsonfile");
}
} // namespace
+39 -15
View File
@@ -29,6 +29,8 @@
#include "iocsh.h"
#include "osiFileName.h"
#include <pv/qsrv.h>
extern "C" int softIocPVA_registerRecordDeviceDriver(struct dbBase *pdbbase);
#ifndef EPICS_BASE
@@ -50,13 +52,15 @@ extern "C" int softIocPVA_registerRecordDeviceDriver(struct dbBase *pdbbase);
namespace {
bool verbose = false;
static void exitSubroutine(subRecord *precord) {
epicsExitLater((precord->a == 0.0) ? EXIT_SUCCESS : EXIT_FAILURE);
}
void usage(const char *arg0, const std::string& base_dbd) {
std::cout<<"Usage: "<<arg0<<
" [-D softIocPVA.dbd] [-h] [-S] [-s] [-a ascf]\n"
" [-D softIocPVA.dbd] [-h] [-S] [-s] [-v] [-a ascf]\n"
"[-m macro=value,macro2=value2] [-d file.db]\n"
"[-x prefix] [st.cmd]\n"
"\n"
@@ -69,9 +73,13 @@ void usage(const char *arg0, const std::string& base_dbd) {
"\n"
" -s Previously caused a shell to be started. Now accepted and ignored.\n"
"\n"
" -v Verbose, display steps taken during startup.\n"
"\n"
" -a <acf> Access Security configuration file. Macro substitution is\n"
" performed.\n"
"\n"
" -G <json> DB Group definition file in JSON format.\n"
"\n"
" -m <MAC>=<value>,... Set/replace macro definitions used by subsequent -d and\n"
" -a.\n"
"\n"
@@ -106,12 +114,14 @@ void lazy_dbd(const std::string& dbd_file) {
if(lazy_dbd_loaded) return;
lazy_dbd_loaded = true;
if (verbose)
std::cout<<"dbLoadDatabase(\""<<dbd_file<<"\")\n";
errIf(dbLoadDatabase(dbd_file.c_str(), NULL, NULL),
std::string("Failed to load DBD file: ")+dbd_file);
std::cout<<"dbLoadDatabase(\""<<dbd_file<<"\")\n";
if (verbose)
std::cout<<"softIocPVA_registerRecordDeviceDriver(pdbbase)\n";
softIocPVA_registerRecordDeviceDriver(pdbbase);
std::cout<<"softIocPVA_registerRecordDeviceDriver(pdbbase)\n";
registryFunctionAdd("exit", (REGISTRYFUNCTION) exitSubroutine);
}
@@ -126,6 +136,7 @@ int main(int argc, char *argv[])
xmacro;
bool interactive = true;
bool loadedDb = false;
bool ranScript = false;
#ifdef USE_EXECDIR
// attempt to compute relative paths
@@ -149,7 +160,7 @@ int main(int argc, char *argv[])
int opt;
while ((opt = getopt(argc, argv, "ha:D:d:m:Ssx:")) != -1) {
while ((opt = getopt(argc, argv, "ha:D:d:m:Ssx:G:v")) != -1) {
switch (opt) {
case 'h': /* Print usage */
usage(argv[0], dbd_file);
@@ -163,13 +174,15 @@ int main(int argc, char *argv[])
case 'a':
lazy_dbd(dbd_file);
if (!macros.empty()) {
if (verbose)
std::cout<<"asSetSubstitutions(\""<<macros<<"\")\n";
if(asSetSubstitutions(macros.c_str()))
throw std::bad_alloc();
std::cout<<"asSetSubstitutions(\""<<macros<<"\")\n";
}
if (verbose)
std::cout<<"asSetFilename(\""<<optarg<<"\")\n";
if(asSetFilename(optarg))
throw std::bad_alloc();
std::cout<<"asSetFilename(\""<<optarg<<"\")\n";
break;
case 'D':
if(lazy_dbd_loaded) {
@@ -179,12 +192,14 @@ int main(int argc, char *argv[])
break;
case 'd':
lazy_dbd(dbd_file);
if (verbose) {
std::cout<<"dbLoadRecords(\""<<optarg<<"\"";
if(!macros.empty())
std::cout<<", \""<<macros<<"\"";
std::cout<<")\n";
}
errIf(dbLoadRecords(optarg, macros.c_str()),
std::string("Failed to load: ")+optarg);
std::cout<<"dbLoadRecords(\""<<optarg<<"\"";
if(!macros.empty())
std::cout<<", \""<<macros<<"\"";
std::cout<<")\n";
loadedDb = true;
break;
case 'm':
@@ -195,6 +210,9 @@ int main(int argc, char *argv[])
break;
case 's':
break; // historical
case 'v':
verbose = true;
break;
case 'x':
lazy_dbd(dbd_file);
xmacro = "IOC=";
@@ -203,6 +221,9 @@ int main(int argc, char *argv[])
std::string("Failed to load: ")+exit_file);
loadedDb = true;
break;
case 'G':
dbLoadGroup(optarg);
break;
}
}
@@ -212,17 +233,20 @@ int main(int argc, char *argv[])
// run script
// ignore any extra positional args (historical)
std::cout<<"# Begin "<<argv[optind]<<"\n";
if (verbose)
std::cout<<"# Begin "<<argv[optind]<<"\n";
errIf(iocsh(argv[optind]),
std::string("Error in ")+argv[optind]);
std::cout<<"# End "<<argv[optind]<<"\n";
if (verbose)
std::cout<<"# End "<<argv[optind]<<"\n";
epicsThreadSleep(0.2);
loadedDb = true; /* Give it the benefit of the doubt... */
ranScript = true; /* Assume the script has done any necessary initialization */
}
if (loadedDb) {
std::cout<<"iocInit()\n";
if (verbose)
std::cout<<"iocInit()\n";
iocInit();
epicsThreadSleep(0.2);
}
@@ -236,7 +260,7 @@ int main(int argc, char *argv[])
}
} else {
if (loadedDb) {
if (loadedDb || ranScript) {
epicsThreadExitMain();
} else {
+4
View File
@@ -0,0 +1,4 @@
record (waveform, "TEST") {
field(FTVL, "SHORT")
field(NELM, "10")
}
+6 -5
View File
@@ -18,6 +18,7 @@ void test_parse()
" \"+atomic\":false,\n"
" \"fld\":{\n"
" \"+type\": \"simple\","
" \"+channel\": \"VAL\","
" \"+putorder\": -4"
" },\n"
" \"\":{\n"
@@ -29,7 +30,7 @@ void test_parse()
"}";
GroupConfig conf;
GroupConfig::parse(txt, conf);
GroupConfig::parse(txt, "rec", conf);
testOk(conf.warning.empty(), "Warnings: %s", conf.warning.c_str());
@@ -37,7 +38,7 @@ void test_parse()
testOk1(conf.groups["grpa"].atomic_set);
testEqual(conf.groups["grpa"].fields["fld"].type, "simple");
testEqual(conf.groups["grpa"].fields["fld"].channel, "");
testEqual(conf.groups["grpa"].fields["fld"].channel, "rec.VAL");
testEqual(conf.groups["grpa"].fields["fld"].putorder, -4);
testEqual(conf.groups["grpa"].fields[""].type, "top");
@@ -49,15 +50,15 @@ void test_fail()
{
GroupConfig conf;
testThrows(std::runtime_error, GroupConfig::parse("{", conf));
testThrows(std::runtime_error, GroupConfig::parse("{", "", conf));
}
{
GroupConfig conf;
testThrows(std::runtime_error, GroupConfig::parse("{\"G\":{\"F\":{\"K\":{}}}}", conf));
testThrows(std::runtime_error, GroupConfig::parse("{\"G\":{\"F\":{\"K\":{}}}}", "", conf));
}
{
GroupConfig conf;
testThrows(std::runtime_error, GroupConfig::parse("{\"G\":5}", conf));
testThrows(std::runtime_error, GroupConfig::parse("{\"G\":5}", "", conf));
}
}
+71 -3
View File
@@ -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,10 +308,49 @@ void testGroupMonitorTriggers(pvac::ClientProvider& client)
testOk1(!mon.poll());
#else
testSkip(19, "No multilock");
testSkip(21, "No multilock");
#endif
}
void testFilters(pvac::ClientProvider& client)
{
testDiag("test w/ server side filters");
pvd::shared_vector<const pvd::int16> expected;
{
pvd::shared_vector<pvd::int16> scratch(9);
scratch[0] = 9;
scratch[1] = 8;
scratch[2] = 7;
scratch[3] = 6;
scratch[4] = 5;
scratch[5] = 4;
scratch[6] = 3;
scratch[7] = 2;
scratch[8] = 1;
expected = pvd::freeze(scratch);
}
client.connect("TEST").put().set("value", expected).exec();
pvd::PVStructure::const_shared_pointer root(client.connect("TEST").get());
testFieldEqual<pvd::PVShortArray>(root, "value", expected);
root = client.connect("TEST.{\"arr\":{\"i\":2}}").get();
{
pvd::shared_vector<pvd::int16> scratch(5);
scratch[0] = 9;
scratch[1] = 7;
scratch[2] = 5;
scratch[3] = 3;
scratch[4] = 1;
expected = pvd::freeze(scratch);
}
testFieldEqual<pvd::PVShortArray>(root, "value", expected);
}
} // namespace
extern "C"
@@ -299,7 +358,7 @@ void p2pTestIoc_registerRecordDeviceDriver(struct dbBase *);
MAIN(testpdb)
{
testPlan(93);
testPlan(99);
try{
QSRVRegistrar_counters();
epics::RefSnapshot ref_before;
@@ -318,6 +377,7 @@ MAIN(testpdb)
#ifdef USE_MULTILOCK
testdbReadDatabase("testpdb-groups.db", NULL, NULL);
#endif
testdbReadDatabase("testfilters.db", NULL, NULL);
IOC.init();
@@ -333,10 +393,16 @@ MAIN(testpdb)
testSingleMonitor(client);
testGroupMonitor(client);
testGroupMonitorTriggers(client);
testFilters(client);
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();
@@ -358,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
View File
@@ -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();
+36 -2
View File
@@ -8,7 +8,7 @@ record(ai, "target:ai") {
}
record(int64in, "src:i1") {
field(INP, {pva:"target:i"})
field(INP, {"pva":"target:i"})
}
# used by testPut()
@@ -17,5 +17,39 @@ record(int64in, "target:i2") {
}
record(int64out, "src:o2") {
field(OUT, {pva:"target:i2"})
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")
}
+172 -58
View File
@@ -72,40 +72,60 @@ void testScalar()
testEqual(dbChannelFinalFieldType(chan_mbbi), DBR_ENUM);
#ifdef USE_INT64
testEqual(dbChannelFinalFieldType(chan_i64), DBR_INT64);
#else
testSkip(1, "!USE_INT64");
#endif
ScalarBuilder builder;
pvd::FieldConstPtr dtype_li(builder.dtype(chan_li));
pvd::PVStructurePtr root;
p2p::auto_ptr<PVIF> pvif_li;
#ifdef USE_INT64
pvd::FieldConstPtr dtype_i64(builder.dtype(chan_i64));
p2p::auto_ptr<PVIF> pvif_i64;
#endif
pvd::FieldConstPtr dtype_si(builder.dtype(chan_si));
pvd::FieldConstPtr dtype_ai(builder.dtype(chan_ai));
pvd::FieldConstPtr dtype_ai_rval(builder.dtype(chan_ai_rval));
pvd::FieldConstPtr dtype_mbbi(builder.dtype(chan_mbbi));
pvd::StructureConstPtr dtype_root(pvd::getFieldCreate()->createFieldBuilder()
->add("li", dtype_li)
p2p::auto_ptr<PVIF> pvif_si;
p2p::auto_ptr<PVIF> pvif_ai;
p2p::auto_ptr<PVIF> pvif_ai_rval;
p2p::auto_ptr<PVIF> pvif_mbbi;
{
ScalarBuilder builder_li(chan_li);
#ifdef USE_INT64
->add("i64", dtype_i64)
ScalarBuilder builder_i64(chan_i64);
#endif
->add("si", dtype_si)
->add("ai", dtype_ai)
->add("ai_rval", dtype_ai_rval)
->add("mbbi", dtype_mbbi)
->createStructure());
ScalarBuilder builder_si(chan_si);
ScalarBuilder builder_ai(chan_ai);
ScalarBuilder builder_ai_rval(chan_ai_rval);
ScalarBuilder builder_mbbi(chan_mbbi);
pvd::PVStructurePtr root(pvd::getPVDataCreate()->createPVStructure(dtype_root));
pvd::FieldConstPtr dtype_li(builder_li.dtype());
#ifdef USE_INT64
pvd::FieldConstPtr dtype_i64(builder_i64.dtype());
#endif
pvd::FieldConstPtr dtype_si(builder_si.dtype());
pvd::FieldConstPtr dtype_ai(builder_ai.dtype());
pvd::FieldConstPtr dtype_ai_rval(builder_ai_rval.dtype());
pvd::FieldConstPtr dtype_mbbi(builder_mbbi.dtype());
p2p::auto_ptr<PVIF> pvif_li(builder.attach(chan_li, root, FieldName("li")));
#ifdef USE_INT64
p2p::auto_ptr<PVIF> pvif_i64(builder.attach(chan_i64, root, FieldName("i64")));
#endif
p2p::auto_ptr<PVIF> pvif_si(builder.attach(chan_si, root, FieldName("si")));
p2p::auto_ptr<PVIF> pvif_ai(builder.attach(chan_ai, root, FieldName("ai")));
p2p::auto_ptr<PVIF> pvif_ai_rval(builder.attach(chan_ai_rval, root, FieldName("ai_rval")));
p2p::auto_ptr<PVIF> pvif_mbbi(builder.attach(chan_mbbi, root, FieldName("mbbi")));
pvd::StructureConstPtr dtype_root(pvd::getFieldCreate()->createFieldBuilder()
->add("li", dtype_li)
#ifdef USE_INT64
->add("i64", dtype_i64)
#endif
->add("si", dtype_si)
->add("ai", dtype_ai)
->add("ai_rval", dtype_ai_rval)
->add("mbbi", dtype_mbbi)
->createStructure());
root = pvd::getPVDataCreate()->createPVStructure(dtype_root);
pvif_li.reset(builder_li.attach(root, FieldName("li")));
#ifdef USE_INT64
pvif_i64.reset(builder_i64.attach(root, FieldName("i64")));
#endif
pvif_si.reset(builder_si.attach(root, FieldName("si")));
pvif_ai.reset(builder_ai.attach(root, FieldName("ai")));
pvif_ai_rval.reset(builder_ai_rval.attach(root, FieldName("ai_rval")));
pvif_mbbi.reset(builder_mbbi.attach(root, FieldName("mbbi")));
}
testShow()<<"Entire structure\n"<<root;
@@ -118,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"))
@@ -150,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"))
@@ -173,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;
@@ -182,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"))
@@ -189,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"))
@@ -210,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"))
@@ -217,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"))
@@ -236,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"))
@@ -297,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");
@@ -362,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);
@@ -424,30 +472,37 @@ void testPlain()
DBCH chan_ai("test:ai");
DBCH chan_mbbi("test:mbbi");
p2p::auto_ptr<PVIFBuilder> builder;
pvd::PVStructurePtr root;
p2p::auto_ptr<PVIF> pvif_li;
p2p::auto_ptr<PVIF> pvif_si;
p2p::auto_ptr<PVIF> pvif_ai;
p2p::auto_ptr<PVIF> pvif_mbbi;
{
builder.reset(PVIFBuilder::create("plain"));
p2p::auto_ptr<PVIFBuilder> builder_li(PVIFBuilder::create("plain", chan_li));
p2p::auto_ptr<PVIFBuilder> builder_si(PVIFBuilder::create("plain", chan_si));
p2p::auto_ptr<PVIFBuilder> builder_ai(PVIFBuilder::create("plain", chan_ai));
p2p::auto_ptr<PVIFBuilder> builder_mbbi(PVIFBuilder::create("plain", chan_mbbi));
pvd::FieldConstPtr dtype_li(builder_li->dtype());
pvd::FieldConstPtr dtype_si(builder_si->dtype());
pvd::FieldConstPtr dtype_ai(builder_ai->dtype());
pvd::FieldConstPtr dtype_mbbi(builder_mbbi->dtype());
pvd::StructureConstPtr dtype_root(pvd::getFieldCreate()->createFieldBuilder()
->add("li", dtype_li)
->add("si", dtype_si)
->add("ai", dtype_ai)
->add("mbbi", dtype_mbbi)
->createStructure());
root = pvd::getPVDataCreate()->createPVStructure(dtype_root);
pvif_li.reset(builder_li->attach(root, FieldName("li")));
pvif_si.reset(builder_si->attach(root, FieldName("si")));
pvif_ai.reset(builder_ai->attach(root, FieldName("ai")));
pvif_mbbi.reset(builder_mbbi->attach(root, FieldName("mbbi")));
}
pvd::FieldConstPtr dtype_li(builder->dtype(chan_li));
pvd::FieldConstPtr dtype_si(builder->dtype(chan_si));
pvd::FieldConstPtr dtype_ai(builder->dtype(chan_ai));
pvd::FieldConstPtr dtype_mbbi(builder->dtype(chan_mbbi));
pvd::StructureConstPtr dtype_root(pvd::getFieldCreate()->createFieldBuilder()
->add("li", dtype_li)
->add("si", dtype_si)
->add("ai", dtype_ai)
->add("mbbi", dtype_mbbi)
->createStructure());
pvd::PVStructurePtr root(pvd::getPVDataCreate()->createPVStructure(dtype_root));
p2p::auto_ptr<PVIF> pvif_li(builder->attach(chan_li, root, FieldName("li")));
p2p::auto_ptr<PVIF> pvif_si(builder->attach(chan_si, root, FieldName("si")));
p2p::auto_ptr<PVIF> pvif_ai(builder->attach(chan_ai, root, FieldName("ai")));
p2p::auto_ptr<PVIF> pvif_mbbi(builder->attach(chan_mbbi, root, FieldName("mbbi")));
pvd::BitSet mask;
mask.clear();
@@ -521,15 +576,73 @@ void testPlain()
dbScanUnlock((dbCommon*)prec_mbbi);
}
void testFilters()
{
testDiag("testFilter");
#if EPICS_VERSION_INT < VERSION_INT(7, 0, 0, 0)
testSkip(5, "Needs Base >=7.0");
#else
TestIOC IOC;
testdbReadDatabase("p2pTestIoc.dbd", NULL, NULL);
p2pTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("testfilters.db", NULL, NULL);
IOC.init();
static const epicsInt32 arr[] = {9, 8, 7, 6, 5, 4, 3, 2, 1};
testdbPutArrFieldOk("TEST", DBF_LONG, 9, arr);
#if EPICS_VERSION_INT > VERSION_INT(7, 0, 5, 0)
testdbGetArrFieldEqual("TEST", DBF_LONG, 10, 9, arr);
testdbGetArrFieldEqual("TEST.{\"arr\":{\"s\":5}}", DBF_LONG, 10, 4, arr+5);
static const epicsInt32 arr2[] = {9, 7, 5, 3, 1};
testdbGetArrFieldEqual("TEST.{\"arr\":{\"i\":2}}", DBF_LONG, 10, 5, arr2);
#else
testSkip(3, "dbUnitTest doesn't use dbChannel");
#endif
pvd::PVStructurePtr root;
p2p::auto_ptr<PVIF> pvif;
DBCH chan("TEST.{\"arr\":{\"i\":2}}");
ScalarBuilder builder(chan);
root = pvd::FieldBuilder::begin()
->add("dut", builder.dtype())
->createStructure()->build();
pvif.reset(builder.attach(root, FieldName("dut")));
LocalFL fl(0, chan.chan);
pvd::shared_vector<pvd::int16> scratch(5);
scratch[0] = 9;
scratch[1] = 7;
scratch[2] = 5;
scratch[3] = 3;
scratch[4] = 1;
pvd::shared_vector<const pvd::int16> expected(pvd::freeze(scratch));
dbCommon *prec = testdbRecordPtr("TEST");
dbScanLock(prec);
pvd::BitSet changed;
pvif->put(changed, DBE_VALUE, fl.pfl);
dbScanUnlock(prec);
testFieldEqual<pvd::PVShortArray>(root, "dut.value", expected);
#endif // >= 7.0
}
} // namespace
MAIN(testpvif)
{
testPlan(75
#ifdef USE_INT64
+13
#endif
);
testPlan(98);
#ifdef USE_INT64
testDiag("Testing of 64-bit field access");
#else
@@ -537,5 +650,6 @@ MAIN(testpvif)
#endif
testScalar();
testPlain();
testFilters();
return testDone();
}