Compare commits
80 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 | |||
| 466d41ebb9 | |||
| d18e2219b3 | |||
| ff22538129 | |||
| 67e668795e | |||
| 1ac2e6c809 | |||
| 40b327cfcb | |||
| 20edb8fdf5 | |||
| babda345b4 | |||
| 04fbaf5a2e | |||
| 3438fde276 | |||
| b515fd30ca | |||
| ad8b77e19f | |||
| b8389ac6a1 | |||
| d47008ee53 | |||
| a709854f0d | |||
| 527afaf856 | |||
| d65af720d1 | |||
| 503cf414be | |||
| 1682c991d2 | |||
| c68c0038e6 | |||
| b69c25feb6 | |||
| eae493e732 | |||
| 29d00e7e38 | |||
| 1335d75403 | |||
| c32cc1ba3c | |||
| f5278ef4ac | |||
| b9d3ad8f90 | |||
| 0e772037ee | |||
| d100eac09e | |||
| 740aad6712 | |||
| c66f224602 | |||
| b1d28cca39 | |||
| 50be5b6025 | |||
| 8e7a1ad295 | |||
| e894ae753f | |||
| 4559f483bf | |||
| e843db5d27 | |||
| 8363c870bd | |||
| 9499137bb4 | |||
| c4798dc2bc | |||
| 11eeed9d0c | |||
| 94d1eedc75 | |||
| cb13435d15 | |||
| cda2222ed5 | |||
| 68708ff530 | |||
| 137ecfed56 | |||
| 29a6f261dc | |||
| 1d9fbbea0b | |||
| 536f4dd02f | |||
| 21ae754869 |
+103
@@ -0,0 +1,103 @@
|
|||||||
|
# .appveyor.yml for use with EPICS Base ci-scripts
|
||||||
|
# (see: https://github.com/epics-base/ci-scripts)
|
||||||
|
|
||||||
|
# This is YAML - indentation levels are crucial
|
||||||
|
|
||||||
|
cache:
|
||||||
|
- C:\Users\appveyor\.tools
|
||||||
|
|
||||||
|
#---------------------------------#
|
||||||
|
# additional packages #
|
||||||
|
#---------------------------------#
|
||||||
|
|
||||||
|
install:
|
||||||
|
# for the sequencer
|
||||||
|
- cinst re2c
|
||||||
|
- cmd: git submodule update --init --recursive
|
||||||
|
|
||||||
|
#---------------------------------#
|
||||||
|
# repository cloning #
|
||||||
|
#---------------------------------#
|
||||||
|
|
||||||
|
init:
|
||||||
|
# Set autocrlf to make batch files work
|
||||||
|
- git config --global core.autocrlf true
|
||||||
|
|
||||||
|
clone_depth: 50
|
||||||
|
|
||||||
|
# Skipping commits affecting only specific files
|
||||||
|
skip_commits:
|
||||||
|
files:
|
||||||
|
- 'documentation/*'
|
||||||
|
- '**/*.md'
|
||||||
|
- '.github/**'
|
||||||
|
|
||||||
|
# Build Configurations: dll/static, regular/debug
|
||||||
|
configuration:
|
||||||
|
- dynamic
|
||||||
|
- static
|
||||||
|
- dynamic-debug
|
||||||
|
- static-debug
|
||||||
|
|
||||||
|
# Environment variables: compiler toolchain, base version, setup file, ...
|
||||||
|
environment:
|
||||||
|
# common / default variables for all jobs
|
||||||
|
SETUP_PATH: .ci-local:.ci
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||||
|
CMP: vs2019
|
||||||
|
BASE: 7.0
|
||||||
|
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
||||||
|
CMP: gcc
|
||||||
|
BASE: 7.0
|
||||||
|
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||||
|
CMP: vs2017
|
||||||
|
BASE: 7.0
|
||||||
|
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||||
|
CMP: vs2019
|
||||||
|
BASE: 3.15
|
||||||
|
|
||||||
|
# Platform: processor architecture
|
||||||
|
platform:
|
||||||
|
- x64
|
||||||
|
|
||||||
|
#---------------------------------#
|
||||||
|
# building & testing #
|
||||||
|
#---------------------------------#
|
||||||
|
|
||||||
|
build_script:
|
||||||
|
- cmd: python .ci/cue.py prepare
|
||||||
|
- cmd: python .ci/cue.py build
|
||||||
|
|
||||||
|
test_script:
|
||||||
|
- cmd: python .ci/cue.py test
|
||||||
|
|
||||||
|
on_finish:
|
||||||
|
- ps: Get-ChildItem *.tap -Recurse -Force | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
|
||||||
|
- cmd: python .ci/cue.py build test-results -s
|
||||||
|
|
||||||
|
#---------------------------------#
|
||||||
|
# debugging #
|
||||||
|
#---------------------------------#
|
||||||
|
|
||||||
|
## if you want to connect by remote desktop to a failed build, uncomment these lines
|
||||||
|
## note that you will need to connect within the usual build timeout limit (60 minutes)
|
||||||
|
## so you may want to adjust the build matrix above to just build the one of interest
|
||||||
|
|
||||||
|
#on_failure:
|
||||||
|
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||||
|
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||||
|
|
||||||
|
#---------------------------------#
|
||||||
|
# notifications #
|
||||||
|
#---------------------------------#
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
|
||||||
|
- provider: Email
|
||||||
|
to:
|
||||||
|
- core-talk@aps.anl.gov
|
||||||
|
on_build_success: false
|
||||||
|
|
||||||
|
- provider: GitHubPullRequest
|
||||||
Submodule
+1
Submodule .ci added at 3db08b5977
@@ -0,0 +1,14 @@
|
|||||||
|
# EPICS Base
|
||||||
|
BASE_DIRNAME=base
|
||||||
|
BASE_REPONAME=epics-base
|
||||||
|
BASE_REPOOWNER=epics-base
|
||||||
|
BASE_VARNAME=EPICS_BASE
|
||||||
|
BASE_RECURSIVE=NO
|
||||||
|
|
||||||
|
MODULES=PVDATA PVACCESS
|
||||||
|
|
||||||
|
PVDATA_REPONAME=pvDataCPP
|
||||||
|
PVDATA_REPOOWNER=epics-base
|
||||||
|
|
||||||
|
PVACCESS_REPONAME=pvAccessCPP
|
||||||
|
PVACCESS_REPOOWNER=epics-base
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -e -x
|
|
||||||
|
|
||||||
make -j2 $EXTRA
|
|
||||||
|
|
||||||
if [ "$TEST" != "NO" ]
|
|
||||||
then
|
|
||||||
make tapfiles
|
|
||||||
make -s test-results
|
|
||||||
fi
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -e -x
|
|
||||||
|
|
||||||
CURDIR="$PWD"
|
|
||||||
|
|
||||||
cat << EOF > $CURDIR/configure/RELEASE.local
|
|
||||||
EPICS_BASE=$HOME/.source/epics-base
|
|
||||||
EOF
|
|
||||||
|
|
||||||
install -d "$HOME/.source"
|
|
||||||
cd "$HOME/.source"
|
|
||||||
|
|
||||||
add_gh_flat() {
|
|
||||||
MODULE=$1
|
|
||||||
REPOOWNER=$2
|
|
||||||
REPONAME=$3
|
|
||||||
BRANCH=$4
|
|
||||||
MODULE_UC=$(echo $MODULE | tr 'a-z' 'A-Z')
|
|
||||||
( git clone --quiet --depth 5 --branch $BRANCH https://github.com/$REPOOWNER/$REPONAME.git $MODULE && \
|
|
||||||
cd $MODULE && git log -n1 )
|
|
||||||
cat < $CURDIR/configure/RELEASE.local > $MODULE/configure/RELEASE.local
|
|
||||||
cat << EOF >> $CURDIR/configure/RELEASE.local
|
|
||||||
${MODULE_UC}=$HOME/.source/$MODULE
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# not recursive
|
|
||||||
git clone --quiet --depth 5 --branch "$BRBASE" https://github.com/${REPOBASE:-epics-base}/epics-base.git epics-base
|
|
||||||
(cd epics-base && git log -n1 )
|
|
||||||
add_gh_flat pvData ${REPOPVD:-epics-base} pvDataCPP ${BRPVD:-master}
|
|
||||||
add_gh_flat pvAccess ${REPOPVA:-epics-base} pvAccessCPP ${BRPVA:-master}
|
|
||||||
|
|
||||||
if [ -e $CURDIR/configure/RELEASE.local ]
|
|
||||||
then
|
|
||||||
cat $CURDIR/configure/RELEASE.local
|
|
||||||
fi
|
|
||||||
|
|
||||||
EPICS_HOST_ARCH=`sh epics-base/startup/EpicsHostArch`
|
|
||||||
|
|
||||||
# requires wine and g++-mingw-w64-i686
|
|
||||||
if [ "$WINE" = "32" ]
|
|
||||||
then
|
|
||||||
echo "Cross mingw32"
|
|
||||||
sed -i -e '/CMPLR_PREFIX/d' epics-base/configure/os/CONFIG_SITE.linux-x86.win32-x86-mingw
|
|
||||||
cat << EOF >> epics-base/configure/os/CONFIG_SITE.linux-x86.win32-x86-mingw
|
|
||||||
CMPLR_PREFIX=i686-w64-mingw32-
|
|
||||||
EOF
|
|
||||||
cat << EOF >> epics-base/configure/CONFIG_SITE
|
|
||||||
CROSS_COMPILER_TARGET_ARCHS+=win32-x86-mingw
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$STATIC" = "YES" ]
|
|
||||||
then
|
|
||||||
echo "Build static libraries/executables"
|
|
||||||
cat << EOF >> epics-base/configure/CONFIG_SITE
|
|
||||||
SHARED_LIBRARIES=NO
|
|
||||||
STATIC_BUILD=YES
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$CMPLR" in
|
|
||||||
clang)
|
|
||||||
echo "Host compiler is clang"
|
|
||||||
cat << EOF >> epics-base/configure/os/CONFIG_SITE.Common.$EPICS_HOST_ARCH
|
|
||||||
GNU = NO
|
|
||||||
CMPLR_CLASS = clang
|
|
||||||
CC = clang
|
|
||||||
CCC = clang++
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# hack
|
|
||||||
sed -i -e 's/CMPLR_CLASS = gcc/CMPLR_CLASS = clang/' epics-base/configure/CONFIG.gnuCommon
|
|
||||||
|
|
||||||
clang --version
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Host compiler is default"
|
|
||||||
gcc --version
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
cat <<EOF >> epics-base/configure/CONFIG_SITE
|
|
||||||
USR_CPPFLAGS += $USR_CPPFLAGS
|
|
||||||
USR_CFLAGS += $USR_CFLAGS
|
|
||||||
USR_CXXFLAGS += $USR_CXXFLAGS
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# set RTEMS to eg. "4.9" or "4.10"
|
|
||||||
# requires qemu, bison, flex, texinfo, install-info
|
|
||||||
if [ -n "$RTEMS" ]
|
|
||||||
then
|
|
||||||
echo "Cross RTEMS${RTEMS} for pc386"
|
|
||||||
curl -L "https://github.com/mdavidsaver/rsb/releases/download/20171203-${RTEMS}/i386-rtems${RTEMS}-trusty-20171203-${RTEMS}.tar.bz2" \
|
|
||||||
| tar -C / -xmj
|
|
||||||
|
|
||||||
sed -i -e '/^RTEMS_VERSION/d' -e '/^RTEMS_BASE/d' epics-base/configure/os/CONFIG_SITE.Common.RTEMS
|
|
||||||
cat << EOF >> epics-base/configure/os/CONFIG_SITE.Common.RTEMS
|
|
||||||
RTEMS_VERSION=$RTEMS
|
|
||||||
RTEMS_BASE=$HOME/.rtems
|
|
||||||
EOF
|
|
||||||
cat << EOF >> epics-base/configure/CONFIG_SITE
|
|
||||||
CROSS_COMPILER_TARGET_ARCHS += RTEMS-pc386-qemu
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
||||||
make -j2 -C epics-base $EXTRA
|
|
||||||
make -j2 -C pvData $EXTRA
|
|
||||||
make -j2 -C pvAccess $EXTRA
|
|
||||||
|
|
||||||
find epics-base/include
|
|
||||||
find epics-base/lib
|
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
# .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@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
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule ".ci"]
|
||||||
|
path = .ci
|
||||||
|
url = https://github.com/epics-base/ci-scripts
|
||||||
-30
@@ -1,30 +0,0 @@
|
|||||||
sudo: false
|
|
||||||
dist: trusty
|
|
||||||
language: c++
|
|
||||||
compiler:
|
|
||||||
- gcc
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- libreadline6-dev
|
|
||||||
- libncurses5-dev
|
|
||||||
- perl
|
|
||||||
- clang
|
|
||||||
- g++-mingw-w64-i686
|
|
||||||
- qemu-system-x86
|
|
||||||
install:
|
|
||||||
- ./.ci/travis-prepare.sh
|
|
||||||
script:
|
|
||||||
- ./.ci/travis-build.sh
|
|
||||||
env:
|
|
||||||
- BRBASE=7.0
|
|
||||||
- BRBASE=7.0 CMPLR=clang
|
|
||||||
- BRBASE=7.0 EXTRA="CMD_CXXFLAGS=-std=c++98"
|
|
||||||
- BRBASE=7.0 EXTRA="CMD_CXXFLAGS=-std=c++11"
|
|
||||||
- BRBASE=7.0 CMPLR=clang EXTRA="CMD_CXXFLAGS=-std=c++11"
|
|
||||||
- BRBASE=7.0 WINE=32 TEST=NO STATIC=YES
|
|
||||||
- BRBASE=7.0 WINE=32 TEST=NO STATIC=NO
|
|
||||||
- BRBASE=7.0 RTEMS=4.10 TEST=NO
|
|
||||||
- BRBASE=7.0 RTEMS=4.9 TEST=NO
|
|
||||||
- BRBASE=3.16
|
|
||||||
- BRBASE=3.15
|
|
||||||
@@ -38,7 +38,7 @@ PROJECT_NAME = pva2pva
|
|||||||
# could be handy for archiving the generated documentation or if some version
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 1.2.2-dev
|
PROJECT_NUMBER = 1.4.2-dev
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# 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
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ p2p
|
|||||||
A PV Access gateway (aka proxy).
|
A PV Access gateway (aka proxy).
|
||||||
The 'p2p' executable.
|
The 'p2p' executable.
|
||||||
|
|
||||||
|
The P2P gateway has been deprecated in favor of
|
||||||
|
[p4p.gw](https://mdavidsaver.github.io/p4p/gw.html).
|
||||||
|
|
||||||
Dependencies
|
Dependencies
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
#include "weakmap.h"
|
#include "weakmap.h"
|
||||||
#include "weakset.h"
|
#include "weakset.h"
|
||||||
|
|
||||||
#include <shareLib.h>
|
|
||||||
|
|
||||||
struct TestPV;
|
struct TestPV;
|
||||||
struct TestPVChannel;
|
struct TestPVChannel;
|
||||||
struct TestPVMonitor;
|
struct TestPVMonitor;
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
# Module (source) version
|
# Module (source) version
|
||||||
EPICS_QSRV_MAJOR_VERSION = 1
|
EPICS_QSRV_MAJOR_VERSION = 1
|
||||||
EPICS_QSRV_MINOR_VERSION = 2
|
EPICS_QSRV_MINOR_VERSION = 4
|
||||||
EPICS_QSRV_MAINTENANCE_VERSION = 2
|
EPICS_QSRV_MAINTENANCE_VERSION = 2
|
||||||
|
|
||||||
# ABI version
|
# ABI version
|
||||||
EPICS_QSRV_ABI_MAJOR_VERSION = 1
|
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
|
# 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
|
# Immediately after a release the MAINTENANCE_VERSION
|
||||||
# will be incremented and the DEVELOPMENT_FLAG set to 1
|
# 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
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 1.2.1
|
PROJECT_NUMBER = 1.4.2-dev
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# 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
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ runs inside an EPICS IOC process and allows clients
|
|||||||
to make requests to access the Process Variables (PVs)
|
to make requests to access the Process Variables (PVs)
|
||||||
within.
|
within.
|
||||||
|
|
||||||
Documentation of @ref qsrv_config including @ref qsrv_group_def
|
Documentation of @ref qsrv_config including @ref qsrv_group_def ,
|
||||||
|
@ref qsrv_aslib
|
||||||
and @ref qsrv_link configuration.
|
and @ref qsrv_link configuration.
|
||||||
|
|
||||||
- @ref release_notes
|
- @ref release_notes
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ which is also the PV name.
|
|||||||
So unlike records, the "field" of a group have a different meaning.
|
So unlike records, the "field" of a group have a different meaning.
|
||||||
Group field names are _not_ part of the PV name.
|
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:
|
For example of a group including two records is:
|
||||||
|
|
||||||
@code
|
@code
|
||||||
@@ -43,6 +45,23 @@ record(ai, "rec:Y") {
|
|||||||
}
|
}
|
||||||
@endcode
|
@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".
|
This group, named "grp:name", has two fields "X" and "Y".
|
||||||
|
|
||||||
@code
|
@code
|
||||||
@@ -68,7 +87,6 @@ record(...) {
|
|||||||
info(Q:group, {
|
info(Q:group, {
|
||||||
"<group_name>":{
|
"<group_name>":{
|
||||||
+id:"some/NT:1.0", # top level ID
|
+id:"some/NT:1.0", # top level ID
|
||||||
+meta:"FLD", # map top level alarm/timeStamp
|
|
||||||
+atomic:true, # whether monitors default to multi-locking atomicity
|
+atomic:true, # whether monitors default to multi-locking atomicity
|
||||||
"<field.name>":{
|
"<field.name>":{
|
||||||
+type:"scalar", # controls how map VAL mapped onto <field.name>
|
+type:"scalar", # controls how map VAL mapped onto <field.name>
|
||||||
@@ -76,7 +94,8 @@ record(...) {
|
|||||||
+id:"some/NT:1.0",
|
+id:"some/NT:1.0",
|
||||||
+trigger:"*", # "*" or comma seperated list of <field.name>s
|
+trigger:"*", # "*" or comma seperated list of <field.name>s
|
||||||
+putorder:0, # set for fields where put is allowed, processing done in increasing order
|
+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 "any"
|
||||||
@li "meta"
|
@li "meta"
|
||||||
@li "proc"
|
@li "proc"
|
||||||
|
@li "structure"
|
||||||
|
|
||||||
The "scalar" mapping places an NTScalar or NTScalarArray as a sub-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
|
The "meta" mapping ignores the "value" and places only the alarm and time
|
||||||
meta-data as sub-fields.
|
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.
|
placed in the top-level structure.
|
||||||
|
|
||||||
The "proc" mapping uses neither "value" nor meta-data.
|
The "proc" mapping uses neither "value" nor meta-data.
|
||||||
Instead the target record is processed during a put.
|
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
|
@subsubsection qsrv_group_map_trig Field Update Triggers
|
||||||
|
|
||||||
The field triggers define how changes to the consitutent field
|
The field triggers define how changes to the consitutent field
|
||||||
@@ -166,7 +189,7 @@ record(ai, "...") {
|
|||||||
|
|
||||||
QSRV will enforce an optional access control policy file (.acf) loaded by the usual means (cf. asSetFilename() ).
|
QSRV will enforce an optional access control policy file (.acf) loaded by the usual means (cf. asSetFilename() ).
|
||||||
This policy is applied to both Single and Group PVs. With Group PVs, restrictions are not defined for the group,
|
This policy is applied to both Single and Group PVs. With Group PVs, restrictions are not defined for the group,
|
||||||
but rather for the individual member records. So the same policy will be applied regardess of how a record
|
but rather for the individual member records. The same policy will be applied regardess of how a record
|
||||||
is accessed (individually, or through a group).
|
is accessed (individually, or through a group).
|
||||||
|
|
||||||
Policy application differs from CA (RSRV) in several ways:
|
Policy application differs from CA (RSRV) in several ways:
|
||||||
@@ -179,6 +202,16 @@ against the list of groups of which the client username is a member. Username t
|
|||||||
to QSRV, and depends on IOC host authentication configuration. Note that this is still based on the client provided
|
to QSRV, and depends on IOC host authentication configuration. Note that this is still based on the client provided
|
||||||
username string.
|
username string.
|
||||||
|
|
||||||
|
@code
|
||||||
|
UAG(special) {
|
||||||
|
someone, "role/op"
|
||||||
|
}
|
||||||
|
@endcode
|
||||||
|
|
||||||
|
The "special" UAG will match CA or PVA clients with the username "someone".
|
||||||
|
It will also match a PVA client if the client provided username is a member
|
||||||
|
of the "op" group (supported on POSIX targets and Windows).
|
||||||
|
|
||||||
@subsection qsrv_link PVAccess Links
|
@subsection qsrv_link PVAccess Links
|
||||||
|
|
||||||
When built against Base >= 3.16.1, support is enabled for PVAccess links,
|
When built against Base >= 3.16.1, support is enabled for PVAccess links,
|
||||||
@@ -187,8 +220,6 @@ for PVA links is quite different.
|
|||||||
|
|
||||||
@note The "dbjlr" and "dbpvar" IOC shell command provide information about PVA links in a running IOC.
|
@note The "dbjlr" and "dbpvar" IOC shell command provide information about PVA links in a running IOC.
|
||||||
|
|
||||||
@warning The PVA Link syntax shown below is provisional and subject to change.
|
|
||||||
|
|
||||||
A simple configuration using defaults is
|
A simple configuration using defaults is
|
||||||
|
|
||||||
@code
|
@code
|
||||||
|
|||||||
@@ -2,6 +2,57 @@
|
|||||||
|
|
||||||
@page release_notes Release Notes
|
@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)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
- 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)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
- Bug Fixes
|
||||||
|
- Fix stalled monitor when server side filter drops initial DBE_PROPERTY update.
|
||||||
|
- Respect DISP
|
||||||
|
- Changes
|
||||||
|
- Refreshed softIocPVA to match options in Base.
|
||||||
|
|
||||||
|
Release 1.2.3 (May 2020)
|
||||||
|
========================
|
||||||
|
|
||||||
|
- P2P gateway deprecated in favor of https://mdavidsaver.github.io/p4p/gw.html
|
||||||
|
|
||||||
Release 1.2.2 (Nov 2019)
|
Release 1.2.2 (Nov 2019)
|
||||||
========================
|
========================
|
||||||
|
|
||||||
|
|||||||
@@ -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("image.db","N=TST:image1")
|
||||||
dbLoadRecords("table.db","N=TST:table1")
|
dbLoadRecords("table.db","N=TST:table1")
|
||||||
|
dbLoadRecords("ntenum.db","P=TST:enum1")
|
||||||
|
|
||||||
|
dbLoadGroup("image.json")
|
||||||
|
|
||||||
iocInit()
|
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")
|
||||||
|
}
|
||||||
@@ -14,8 +14,6 @@
|
|||||||
#include "weakmap.h"
|
#include "weakmap.h"
|
||||||
#include "weakset.h"
|
#include "weakset.h"
|
||||||
|
|
||||||
#include <shareLib.h>
|
|
||||||
|
|
||||||
struct ChannelCache;
|
struct ChannelCache;
|
||||||
struct ChannelCacheEntry;
|
struct ChannelCacheEntry;
|
||||||
struct MonitorUser;
|
struct MonitorUser;
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
#include <pv/pvAccess.h>
|
#include <pv/pvAccess.h>
|
||||||
|
|
||||||
#include <shareLib.h>
|
|
||||||
|
|
||||||
#include "chancache.h"
|
#include "chancache.h"
|
||||||
|
|
||||||
struct GWChannel : public epics::pvAccess::Channel
|
struct GWChannel : public epics::pvAccess::Channel
|
||||||
|
|||||||
+13
-2
@@ -139,7 +139,7 @@ GWServerChannelProvider::shared_pointer configure_client(ServerConfig& arg, cons
|
|||||||
|
|
||||||
pva::Configuration::shared_pointer C(pva::ConfigurationBuilder()
|
pva::Configuration::shared_pointer C(pva::ConfigurationBuilder()
|
||||||
.add("EPICS_PVA_ADDR_LIST", conf->getSubFieldT<pvd::PVString>("addrlist")->get())
|
.add("EPICS_PVA_ADDR_LIST", conf->getSubFieldT<pvd::PVString>("addrlist")->get())
|
||||||
.add("EPICS_PVA_AUTO_ADDR_LIST", conf->getSubFieldT<pvd::PVBoolean>("autoaddrlist")->get())
|
.add("EPICS_PVA_AUTO_ADDR_LIST", conf->getSubFieldT<pvd::PVScalar>("autoaddrlist")->getAs<std::string>())
|
||||||
.add("EPICS_PVA_SERVER_PORT", conf->getSubFieldT<pvd::PVScalar>("serverport")->getAs<pvd::uint16>())
|
.add("EPICS_PVA_SERVER_PORT", conf->getSubFieldT<pvd::PVScalar>("serverport")->getAs<pvd::uint16>())
|
||||||
.add("EPICS_PVA_BROADCAST_PORT", conf->getSubFieldT<pvd::PVScalar>("bcastport")->getAs<pvd::uint16>())
|
.add("EPICS_PVA_BROADCAST_PORT", conf->getSubFieldT<pvd::PVScalar>("bcastport")->getAs<pvd::uint16>())
|
||||||
.add("EPICS_PVA_DEBUG", arg.debug>=5 ? 5 : 0)
|
.add("EPICS_PVA_DEBUG", arg.debug>=5 ? 5 : 0)
|
||||||
@@ -163,7 +163,7 @@ pva::ServerContext::shared_pointer configure_server(ServerConfig& arg, const pvd
|
|||||||
pva::Configuration::shared_pointer C(pva::ConfigurationBuilder()
|
pva::Configuration::shared_pointer C(pva::ConfigurationBuilder()
|
||||||
.add("EPICS_PVAS_INTF_ADDR_LIST", conf->getSubFieldT<pvd::PVString>("interface")->get())
|
.add("EPICS_PVAS_INTF_ADDR_LIST", conf->getSubFieldT<pvd::PVString>("interface")->get())
|
||||||
.add("EPICS_PVAS_BEACON_ADDR_LIST", conf->getSubFieldT<pvd::PVString>("addrlist")->get())
|
.add("EPICS_PVAS_BEACON_ADDR_LIST", conf->getSubFieldT<pvd::PVString>("addrlist")->get())
|
||||||
.add("EPICS_PVAS_AUTO_BEACON_ADDR_LIST", conf->getSubFieldT<pvd::PVBoolean>("autoaddrlist")->get())
|
.add("EPICS_PVAS_AUTO_BEACON_ADDR_LIST", conf->getSubFieldT<pvd::PVScalar>("autoaddrlist")->getAs<std::string>())
|
||||||
.add("EPICS_PVAS_SERVER_PORT", conf->getSubFieldT<pvd::PVScalar>("serverport")->getAs<pvd::uint16>())
|
.add("EPICS_PVAS_SERVER_PORT", conf->getSubFieldT<pvd::PVScalar>("serverport")->getAs<pvd::uint16>())
|
||||||
.add("EPICS_PVAS_BROADCAST_PORT", conf->getSubFieldT<pvd::PVScalar>("bcastport")->getAs<pvd::uint16>())
|
.add("EPICS_PVAS_BROADCAST_PORT", conf->getSubFieldT<pvd::PVScalar>("bcastport")->getAs<pvd::uint16>())
|
||||||
.add("EPICS_PVA_DEBUG", arg.debug>=5 ? 5 : 0)
|
.add("EPICS_PVA_DEBUG", arg.debug>=5 ? 5 : 0)
|
||||||
@@ -258,6 +258,17 @@ int main(int argc, char *argv[])
|
|||||||
theserver = &arg;
|
theserver = &arg;
|
||||||
getargs(arg, argc, argv);
|
getargs(arg, argc, argv);
|
||||||
|
|
||||||
|
if(arg.debug>0)
|
||||||
|
std::cout<<"Notice: This p2p gateway prototype has been superceded by the p4p.gw gateway\n"
|
||||||
|
" which has exciting new features including granular access control,\n"
|
||||||
|
" and status PVs including bandwidth usage reports.\n"
|
||||||
|
" p2p is considered deprecated by its author, and will receive\n"
|
||||||
|
" minimal maintainance effort going forward.\n"
|
||||||
|
" Users are encouraged to migrate to p4p.gw.\n"
|
||||||
|
"\n"
|
||||||
|
" https://mdavidsaver.github.io/p4p/gw.html\n"
|
||||||
|
"\n";
|
||||||
|
|
||||||
pva::pvAccessLogLevel lvl;
|
pva::pvAccessLogLevel lvl;
|
||||||
if(arg.debug<0)
|
if(arg.debug<0)
|
||||||
lvl = pva::logLevelError;
|
lvl = pva::logLevelError;
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
|
|
||||||
#include <pv/pvAccess.h>
|
#include <pv/pvAccess.h>
|
||||||
|
|
||||||
#include <shareLib.h>
|
|
||||||
|
|
||||||
typedef epicsGuard<epicsMutex> Guard;
|
typedef epicsGuard<epicsMutex> Guard;
|
||||||
typedef epicsGuardRelease<epicsMutex> UnGuard;
|
typedef epicsGuardRelease<epicsMutex> UnGuard;
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
#include "chancache.h"
|
#include "chancache.h"
|
||||||
#include "channel.h"
|
#include "channel.h"
|
||||||
|
|
||||||
#include <shareLib.h>
|
|
||||||
|
|
||||||
struct GWServerChannelProvider :
|
struct GWServerChannelProvider :
|
||||||
public epics::pvAccess::ChannelProvider,
|
public epics::pvAccess::ChannelProvider,
|
||||||
public epics::pvAccess::ChannelFind,
|
public epics::pvAccess::ChannelFind,
|
||||||
|
|||||||
+3
-1
@@ -60,7 +60,9 @@ qsrv_LIBS += $(EPICS_BASE_IOC_LIBS)
|
|||||||
|
|
||||||
FINAL_LOCATION ?= $(shell $(PERL) $(TOOLS)/fullPathName.pl $(INSTALL_LOCATION))
|
FINAL_LOCATION ?= $(shell $(PERL) $(TOOLS)/fullPathName.pl $(INSTALL_LOCATION))
|
||||||
|
|
||||||
USR_CPPFLAGS += -DFINAL_LOCATION="\"$(FINAL_LOCATION)\""
|
# since we copy softMain.cpp from Base, use the same
|
||||||
|
# EPICS_BASE macro for our location
|
||||||
|
USR_CPPFLAGS += -DEPICS_BASE="\"$(FINAL_LOCATION)\""
|
||||||
|
|
||||||
PROD_IOC += softIocPVA
|
PROD_IOC += softIocPVA
|
||||||
|
|
||||||
|
|||||||
+15
-6
@@ -26,6 +26,7 @@ typedef std::map<std::string, pvd::AnyScalar> options_t;
|
|||||||
typedef std::map<std::string, options_t> config_t;
|
typedef std::map<std::string, options_t> config_t;
|
||||||
|
|
||||||
struct context {
|
struct context {
|
||||||
|
const std::string chanprefix;
|
||||||
std::string msg;
|
std::string msg;
|
||||||
std::string group, field, key;
|
std::string group, field, key;
|
||||||
unsigned depth; // number of '{'s
|
unsigned depth; // number of '{'s
|
||||||
@@ -34,9 +35,13 @@ struct context {
|
|||||||
// depth 2 - Group
|
// depth 2 - Group
|
||||||
// depth 3 - field
|
// 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()
|
void can_assign()
|
||||||
{
|
{
|
||||||
@@ -69,7 +74,7 @@ struct context {
|
|||||||
fld.type = value.ref<std::string>();
|
fld.type = value.ref<std::string>();
|
||||||
|
|
||||||
} else if(key=="+channel") {
|
} else if(key=="+channel") {
|
||||||
fld.channel = value.ref<std::string>();
|
fld.channel = chanprefix + value.ref<std::string>();
|
||||||
|
|
||||||
} else if(key=="+id") {
|
} else if(key=="+id") {
|
||||||
fld.id = value.ref<std::string>();
|
fld.id = value.ref<std::string>();
|
||||||
@@ -217,6 +222,7 @@ struct handler {
|
|||||||
}// namespace
|
}// namespace
|
||||||
|
|
||||||
void GroupConfig::parse(const char *txt,
|
void GroupConfig::parse(const char *txt,
|
||||||
|
const char *recname,
|
||||||
GroupConfig& result)
|
GroupConfig& result)
|
||||||
{
|
{
|
||||||
#ifndef EPICS_YAJL_VERSION
|
#ifndef EPICS_YAJL_VERSION
|
||||||
@@ -228,7 +234,12 @@ void GroupConfig::parse(const char *txt,
|
|||||||
|
|
||||||
std::istringstream strm(txt);
|
std::istringstream strm(txt);
|
||||||
|
|
||||||
context ctxt;
|
std::string chanprefix;
|
||||||
|
if(recname) {
|
||||||
|
chanprefix = recname;
|
||||||
|
chanprefix += '.';
|
||||||
|
}
|
||||||
|
context ctxt(chanprefix, result);
|
||||||
|
|
||||||
#ifndef EPICS_YAJL_VERSION
|
#ifndef EPICS_YAJL_VERSION
|
||||||
handler handle(yajl_alloc(&conf_cbs, &conf, NULL, &ctxt));
|
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))
|
if(!pvd::yajl_parse_helper(strm, handle))
|
||||||
throw std::runtime_error(ctxt.msg);
|
throw std::runtime_error(ctxt.msg);
|
||||||
|
|
||||||
ctxt.conf.swap(result);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
#include <epicsMath.h>
|
#include <epicsMath.h>
|
||||||
#include <dbAccess.h>
|
#include <dbAccess.h>
|
||||||
#include <dbScan.h>
|
#include <dbScan.h>
|
||||||
|
#include <dbLink.h>
|
||||||
#include <recGbl.h>
|
#include <recGbl.h>
|
||||||
#include <alarm.h>
|
#include <alarm.h>
|
||||||
|
|
||||||
|
#include <longinRecord.h>
|
||||||
#include <waveformRecord.h>
|
#include <waveformRecord.h>
|
||||||
#include <menuFtype.h>
|
#include <menuFtype.h>
|
||||||
|
|
||||||
@@ -48,9 +50,48 @@ long process_spin(waveformRecord *prec)
|
|||||||
|
|
||||||
prec->nord = prec->nelm;
|
prec->nord = prec->nelm;
|
||||||
|
|
||||||
|
#ifdef DBRutag
|
||||||
|
prec->utag = (prec->utag+1u)&0x7fffffff;
|
||||||
|
#endif
|
||||||
|
|
||||||
return 0;
|
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>
|
template<typename REC>
|
||||||
struct dset5
|
struct dset5
|
||||||
{
|
{
|
||||||
@@ -63,9 +104,13 @@ struct dset5
|
|||||||
};
|
};
|
||||||
|
|
||||||
dset5<waveformRecord> devWfPDBDemo = {5,0,0,&init_spin,0,&process_spin};
|
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
|
} // namespace
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
epicsExportAddress(dset, devWfPDBDemo);
|
epicsExportAddress(dset, devWfPDBDemo);
|
||||||
|
epicsExportAddress(dset, devWfPDBDemoRepeat);
|
||||||
|
epicsExportAddress(dset, devLoPDBUTag);
|
||||||
}
|
}
|
||||||
|
|||||||
+168
-106
@@ -59,16 +59,14 @@ struct Splitter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct GroupMemberInfo {
|
struct GroupMemberInfo {
|
||||||
// consumes builder
|
GroupMemberInfo() :putorder(0) {}
|
||||||
GroupMemberInfo(const std::string& a, const std::string& b, const std::tr1::shared_ptr<PVIFBuilder>& builder)
|
|
||||||
:pvname(a), pvfldname(b), builder(builder), putorder(0) {}
|
|
||||||
|
|
||||||
std::string pvname, // aka. name passed to dbChannelOpen()
|
std::string pvname, // aka. name passed to dbChannelOpen()
|
||||||
pvfldname; // PVStructure sub-field
|
pvfldname; // PVStructure sub-field
|
||||||
std::string structID; // ID to assign to sub-field
|
std::string structID; // ID to assign to sub-field
|
||||||
|
std::string type; // mapping type
|
||||||
typedef std::set<std::string> triggers_t;
|
typedef std::set<std::string> triggers_t;
|
||||||
triggers_t triggers; // names in GroupInfo::members_names which are post()d on events from pvfldname
|
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;
|
int putorder;
|
||||||
|
|
||||||
bool operator<(const GroupMemberInfo& o) const {
|
bool operator<(const GroupMemberInfo& o) const {
|
||||||
@@ -100,9 +98,6 @@ struct PDBProcessor
|
|||||||
typedef std::map<std::string, GroupInfo> groups_t;
|
typedef std::map<std::string, GroupInfo> groups_t;
|
||||||
groups_t groups;
|
groups_t groups;
|
||||||
|
|
||||||
std::string recbase;
|
|
||||||
GroupInfo *curgroup;
|
|
||||||
|
|
||||||
// validate trigger mappings and process into bit map form
|
// validate trigger mappings and process into bit map form
|
||||||
void resolveTriggers()
|
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())
|
for(pdbRecordIterator rec; !rec.done(); rec.next())
|
||||||
{
|
{
|
||||||
const char *json = rec.info("Q:group");
|
const char *json = rec.info("Q:group");
|
||||||
@@ -189,107 +189,154 @@ struct PDBProcessor
|
|||||||
warned = true;
|
warned = true;
|
||||||
fprintf(stderr, "%s: ignoring info(Q:Group, ...\n", rec.name());
|
fprintf(stderr, "%s: ignoring info(Q:Group, ...\n", rec.name());
|
||||||
}
|
}
|
||||||
#else
|
#endif
|
||||||
if(PDBProviderDebug>2) {
|
if(PDBProviderDebug>2) {
|
||||||
fprintf(stderr, "%s: info(Q:Group, ...\n", rec.name());
|
fprintf(stderr, "%s: info(Q:Group, ...\n", rec.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_MULTILOCK
|
||||||
try {
|
try {
|
||||||
GroupConfig conf;
|
GroupConfig::parse(json, rec.name(), conf);
|
||||||
GroupConfig::parse(json, conf);
|
|
||||||
if(!conf.warning.empty())
|
if(!conf.warning.empty())
|
||||||
fprintf(stderr, "%s: warning(s) from info(Q:group, ...\n%s", rec.name(), conf.warning.c_str());
|
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){
|
}catch(std::exception& e){
|
||||||
fprintf(stderr, "%s: Error parsing info(\"Q:group\", ... : %s\n",
|
fprintf(stderr, "%s: Error parsing info(\"Q:group\", ... : %s\n",
|
||||||
rec.record()->name, e.what());
|
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, 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){
|
||||||
|
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
|
// re-sort GroupInfo::members to ensure the shorter names appear first
|
||||||
@@ -311,6 +358,7 @@ struct PDBProcessor
|
|||||||
resolveTriggers();
|
resolveTriggers();
|
||||||
// must not re-sort members after this point as resolveTriggers()
|
// must not re-sort members after this point as resolveTriggers()
|
||||||
// has stored array indicies.
|
// has stored array indicies.
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -318,6 +366,8 @@ struct PDBProcessor
|
|||||||
|
|
||||||
size_t PDBProvider::num_instances;
|
size_t PDBProvider::num_instances;
|
||||||
|
|
||||||
|
std::list<std::string> PDBProvider::group_files;
|
||||||
|
|
||||||
PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_pointer &)
|
PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_pointer &)
|
||||||
{
|
{
|
||||||
/* Long view
|
/* Long view
|
||||||
@@ -388,6 +438,9 @@ PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_poin
|
|||||||
else
|
else
|
||||||
builder = builder->addNestedStructure(parts[j].name);
|
builder = builder->addNestedStructure(parts[j].name);
|
||||||
}
|
}
|
||||||
|
if(parts.back().isArray()) {
|
||||||
|
builder = builder->addNestedStructureArray(parts.back().name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!mem.structID.empty())
|
if(!mem.structID.empty())
|
||||||
@@ -405,22 +458,32 @@ PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_poin
|
|||||||
chan.swap(temp);
|
chan.swap(temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!parts.empty())
|
std::tr1::shared_ptr<PVIFBuilder> pvifbuilder(PVIFBuilder::create(mem.type, chan.chan));
|
||||||
builder = mem.builder->dtype(builder, parts.back().name, chan);
|
|
||||||
|
if(!parts.empty() && !parts.back().isArray())
|
||||||
|
builder = pvifbuilder->dtype(builder, parts.back().name);
|
||||||
else
|
else
|
||||||
builder = mem.builder->dtype(builder, "", chan);
|
builder = pvifbuilder->dtype(builder, "");
|
||||||
|
|
||||||
if(!parts.empty()) {
|
if(!parts.empty()) {
|
||||||
for(size_t j=0; j<parts.size()-1; j++)
|
for(size_t j=0; j<parts.size()-1; j++)
|
||||||
builder = builder->endNested();
|
builder = builder->endNested();
|
||||||
|
if(parts.back().isArray())
|
||||||
|
builder = builder->endNested();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!mem.pvname.empty()) {
|
if(!mem.pvname.empty()) {
|
||||||
members_map[mem.pvfldname] = J;
|
members_map[mem.pvfldname] = J;
|
||||||
PDBGroupPV::Info& info = members[J];
|
PDBGroupPV::Info& info = members[J];
|
||||||
|
|
||||||
|
DBCH chan2;
|
||||||
|
if(chan.chan && (ellCount(&chan.chan->pre_chain)>0 || ellCount(&chan.chan->post_chain)>0)) {
|
||||||
|
DBCH temp(mem.pvname);
|
||||||
|
info.chan2.swap(chan2);
|
||||||
|
}
|
||||||
|
|
||||||
info.allowProc = mem.putorder != std::numeric_limits<int>::min();
|
info.allowProc = mem.putorder != std::numeric_limits<int>::min();
|
||||||
info.builder = PTRMOVE(mem.builder);
|
info.builder = PTRMOVE(pvifbuilder);
|
||||||
assert(info.builder.get());
|
assert(info.builder.get());
|
||||||
|
|
||||||
info.attachment.swap(parts);
|
info.attachment.swap(parts);
|
||||||
@@ -511,10 +574,11 @@ PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_poin
|
|||||||
info.evt_VALUE.self = info.evt_PROPERTY.self = pv;
|
info.evt_VALUE.self = info.evt_PROPERTY.self = pv;
|
||||||
assert(info.chan);
|
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
|
// TODO: don't need evt_PROPERTY for PVIF plain
|
||||||
info.evt_PROPERTY.create(event_context, info.chan, &pdb_group_event, DBE_PROPERTY);
|
dbChannel *pchan = info.chan2.chan ? info.chan2.chan : info.chan.chan;
|
||||||
|
info.evt_PROPERTY.create(event_context, pchan, &pdb_group_event, DBE_PROPERTY);
|
||||||
|
|
||||||
if(!info.triggers.empty()) {
|
if(!info.triggers.empty()) {
|
||||||
info.evt_VALUE.create(event_context, info.chan, &pdb_group_event, DBE_VALUE|DBE_ALARM);
|
info.evt_VALUE.create(event_context, info.chan, &pdb_group_event, DBE_VALUE|DBE_ALARM);
|
||||||
@@ -685,8 +749,6 @@ FieldName::FieldName(const std::string& pv)
|
|||||||
}
|
}
|
||||||
if(parts.empty())
|
if(parts.empty())
|
||||||
throw std::runtime_error("Empty field name");
|
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
|
epics::pvData::PVFieldPtr
|
||||||
|
|||||||
+21
-1
@@ -2,13 +2,14 @@
|
|||||||
#define PDB_H
|
#define PDB_H
|
||||||
|
|
||||||
#include <dbEvent.h>
|
#include <dbEvent.h>
|
||||||
|
#include <asLib.h>
|
||||||
|
|
||||||
#include <pv/configuration.h>
|
#include <pv/configuration.h>
|
||||||
#include <pv/pvAccess.h>
|
#include <pv/pvAccess.h>
|
||||||
|
|
||||||
#include "weakmap.h"
|
#include "weakmap.h"
|
||||||
|
|
||||||
#include <shareLib.h>
|
#include <pv/qsrv.h>
|
||||||
|
|
||||||
struct PDBProvider;
|
struct PDBProvider;
|
||||||
|
|
||||||
@@ -64,10 +65,29 @@ struct QSRV_API PDBProvider : public epics::pvAccess::ChannelProvider,
|
|||||||
|
|
||||||
dbEventCtx event_context;
|
dbEventCtx event_context;
|
||||||
|
|
||||||
|
typedef std::list<std::string> group_files_t;
|
||||||
|
static group_files_t group_files;
|
||||||
|
|
||||||
static size_t num_instances;
|
static size_t num_instances;
|
||||||
};
|
};
|
||||||
|
|
||||||
QSRV_API
|
QSRV_API
|
||||||
void QSRVRegistrar_counters();
|
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
|
#endif // PDB_H
|
||||||
|
|||||||
+24
-7
@@ -8,6 +8,7 @@
|
|||||||
#include <dbAccess.h>
|
#include <dbAccess.h>
|
||||||
#include <dbChannel.h>
|
#include <dbChannel.h>
|
||||||
#include <dbStaticLib.h>
|
#include <dbStaticLib.h>
|
||||||
|
#include <asLib.h>
|
||||||
|
|
||||||
#include <pv/pvAccess.h>
|
#include <pv/pvAccess.h>
|
||||||
#include <pv/configuration.h>
|
#include <pv/configuration.h>
|
||||||
@@ -334,7 +335,7 @@ PDBGroupPut::PDBGroupPut(const PDBGroupChannel::shared_pointer& channel,
|
|||||||
{
|
{
|
||||||
PDBGroupPV::Info& info = channel->pv->members[i];
|
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
|
// assume value may be a different struct each time... lot of wasted prep work
|
||||||
const size_t npvs = channel->pv->members.size();
|
const size_t npvs = channel->pv->members.size();
|
||||||
std::vector<std::tr1::shared_ptr<PVIF> > putpvif(npvs);
|
std::vector<std::tr1::shared_ptr<PVIF> > putpvif(npvs);
|
||||||
|
pvd::shared_vector<AsWritePvt> asWritePvt(npvs);
|
||||||
|
|
||||||
for(size_t i=0; i<npvs; i++)
|
for(size_t i=0; i<npvs; i++)
|
||||||
{
|
{
|
||||||
PDBGroupPV::Info& info = channel->pv->members[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;
|
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++) {
|
for(size_t i=0; ret && i<npvs; i++) {
|
||||||
if(!putpvif[i].get()) continue;
|
if(!putpvif[i].get()) continue;
|
||||||
|
|
||||||
ret |= putpvif[i]->get(*changed, doProc);
|
ret |= putpvif[i]->get(*changed, doProc, channel->aspvt[i].canWrite());
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -394,8 +408,10 @@ void PDBGroupPut::get()
|
|||||||
changed->clear();
|
changed->clear();
|
||||||
if(atomic) {
|
if(atomic) {
|
||||||
DBManyLocker L(channel->pv->locker);
|
DBManyLocker L(channel->pv->locker);
|
||||||
for(size_t i=0; i<npvs; i++)
|
for(size_t i=0; i<npvs; i++) {
|
||||||
pvif[i]->put(*changed, DBE_VALUE|DBE_ALARM|DBE_PROPERTY, NULL);
|
LocalFL FL(NULL, channel->pv->members[i].chan);
|
||||||
|
pvif[i]->put(*changed, DBE_VALUE|DBE_ALARM|DBE_PROPERTY, FL.pfl);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
for(size_t i=0; i<npvs; i++)
|
for(size_t i=0; i<npvs; i++)
|
||||||
@@ -403,7 +419,8 @@ void PDBGroupPut::get()
|
|||||||
PDBGroupPV::Info& info = channel->pv->members[i];
|
PDBGroupPV::Info& info = channel->pv->members[i];
|
||||||
|
|
||||||
DBScanLocker L(dbChannelRecord(info.chan));
|
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?
|
//TODO: report unused fields as changed?
|
||||||
|
|||||||
+3
-3
@@ -17,8 +17,6 @@
|
|||||||
#include "pvif.h"
|
#include "pvif.h"
|
||||||
#include "pdb.h"
|
#include "pdb.h"
|
||||||
|
|
||||||
#include <shareLib.h>
|
|
||||||
|
|
||||||
struct QSRV_API GroupConfig
|
struct QSRV_API GroupConfig
|
||||||
{
|
{
|
||||||
struct QSRV_API Field {
|
struct QSRV_API Field {
|
||||||
@@ -61,7 +59,7 @@ struct QSRV_API GroupConfig
|
|||||||
std::swap(warning, o.warning);
|
std::swap(warning, o.warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void parse(const char *txt,
|
static void parse(const char *txt, const char *recname,
|
||||||
GroupConfig& result);
|
GroupConfig& result);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -89,6 +87,8 @@ struct QSRV_API PDBGroupPV : public PDBPV
|
|||||||
|
|
||||||
struct Info {
|
struct Info {
|
||||||
DBCH chan;
|
DBCH chan;
|
||||||
|
// used for DBE_PROPERTY subscription when chan has filters
|
||||||
|
DBCH chan2;
|
||||||
std::tr1::shared_ptr<PVIFBuilder> builder;
|
std::tr1::shared_ptr<PVIFBuilder> builder;
|
||||||
FieldName attachment;
|
FieldName attachment;
|
||||||
typedef std::vector<size_t> triggers_t;
|
typedef std::vector<size_t> triggers_t;
|
||||||
|
|||||||
+27
-9
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <asLib.h>
|
||||||
#include <dbAccess.h>
|
#include <dbAccess.h>
|
||||||
#include <dbChannel.h>
|
#include <dbChannel.h>
|
||||||
#include <dbStaticLib.h>
|
#include <dbStaticLib.h>
|
||||||
@@ -96,19 +97,23 @@ void pdb_single_event(void *user_arg, struct dbChannel *chan,
|
|||||||
PDBSinglePV::PDBSinglePV(DBCH& chan,
|
PDBSinglePV::PDBSinglePV(DBCH& chan,
|
||||||
const PDBProvider::shared_pointer& prov)
|
const PDBProvider::shared_pointer& prov)
|
||||||
:provider(prov)
|
:provider(prov)
|
||||||
,builder(new ScalarBuilder)
|
,builder(new ScalarBuilder(chan.chan))
|
||||||
,interested_iterating(false)
|
,interested_iterating(false)
|
||||||
,evt_VALUE(this)
|
,evt_VALUE(this)
|
||||||
,evt_PROPERTY(this)
|
,evt_PROPERTY(this)
|
||||||
,hadevent_VALUE(false)
|
,hadevent_VALUE(false)
|
||||||
,hadevent_PROPERTY(false)
|
,hadevent_PROPERTY(false)
|
||||||
{
|
{
|
||||||
|
if(ellCount(&chan.chan->pre_chain) || ellCount(&chan.chan->post_chain)) {
|
||||||
|
DBCH temp(dbChannelName(chan.chan));
|
||||||
|
this->chan2.swap(temp);
|
||||||
|
}
|
||||||
this->chan.swap(chan);
|
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);
|
complete = pvd::getPVDataCreate()->createPVStructure(fielddesc);
|
||||||
FieldName temp;
|
FieldName temp;
|
||||||
pvif.reset(builder->attach(this->chan, complete, temp));
|
pvif.reset(builder->attach(complete, temp));
|
||||||
|
|
||||||
epics::atomic::increment(num_instances);
|
epics::atomic::increment(num_instances);
|
||||||
}
|
}
|
||||||
@@ -120,8 +125,9 @@ PDBSinglePV::~PDBSinglePV()
|
|||||||
|
|
||||||
void PDBSinglePV::activate()
|
void PDBSinglePV::activate()
|
||||||
{
|
{
|
||||||
|
dbChannel *pchan = this->chan2.chan ? this->chan2.chan : this->chan.chan;
|
||||||
evt_VALUE.create(provider->event_context, this->chan, &pdb_single_event, DBE_VALUE|DBE_ALARM);
|
evt_VALUE.create(provider->event_context, this->chan, &pdb_single_event, DBE_VALUE|DBE_ALARM);
|
||||||
evt_PROPERTY.create(provider->event_context, this->chan, &pdb_single_event, DBE_PROPERTY);
|
evt_PROPERTY.create(provider->event_context, pchan, &pdb_single_event, DBE_PROPERTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
pva::Channel::shared_pointer
|
pva::Channel::shared_pointer
|
||||||
@@ -303,7 +309,7 @@ PDBSinglePut::PDBSinglePut(const PDBSingleChannel::shared_pointer &channel,
|
|||||||
,requester(requester)
|
,requester(requester)
|
||||||
,changed(new pvd::BitSet(channel->fielddesc->getNumberFields()))
|
,changed(new pvd::BitSet(channel->fielddesc->getNumberFields()))
|
||||||
,pvf(pvd::getPVDataCreate()->createPVStructure(channel->fielddesc))
|
,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)
|
,notifyBusy(0)
|
||||||
,doProc(PVIF::ProcPassive)
|
,doProc(PVIF::ProcPassive)
|
||||||
,doWait(false)
|
,doWait(false)
|
||||||
@@ -350,6 +356,17 @@ void PDBSinglePut::put(pvd::PVStructure::shared_pointer const & value,
|
|||||||
dbChannel *chan = channel->pv->chan;
|
dbChannel *chan = channel->pv->chan;
|
||||||
dbFldDes *fld = dbChannelFldDes(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;
|
pvd::Status ret;
|
||||||
if(!channel->aspvt.canWrite()) {
|
if(!channel->aspvt.canWrite()) {
|
||||||
ret = pvd::Status::error("Put not permitted");
|
ret = pvd::Status::error("Put not permitted");
|
||||||
@@ -370,7 +387,7 @@ void PDBSinglePut::put(pvd::PVStructure::shared_pointer const & value,
|
|||||||
// TODO: dbNotify doesn't allow us for force processing
|
// TODO: dbNotify doesn't allow us for force processing
|
||||||
|
|
||||||
// assume value may be a different struct each time
|
// 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);
|
unsigned mask = putpvif->dbe(*changed);
|
||||||
|
|
||||||
if(mask!=DBE_VALUE) {
|
if(mask!=DBE_VALUE) {
|
||||||
@@ -392,10 +409,10 @@ void PDBSinglePut::put(pvd::PVStructure::shared_pointer const & value,
|
|||||||
return; // skip notification
|
return; // skip notification
|
||||||
} else {
|
} else {
|
||||||
// assume value may be a different struct each time
|
// 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{
|
try{
|
||||||
DBScanLocker L(chan);
|
DBScanLocker L(chan);
|
||||||
putpvif->get(*changed, doProc);
|
ret = putpvif->get(*changed, doProc);
|
||||||
|
|
||||||
}catch(std::runtime_error& e){
|
}catch(std::runtime_error& e){
|
||||||
ret = pvd::Status::error(e.what());
|
ret = pvd::Status::error(e.what());
|
||||||
@@ -421,7 +438,8 @@ void PDBSinglePut::get()
|
|||||||
changed->clear();
|
changed->clear();
|
||||||
{
|
{
|
||||||
DBScanLocker L(pvif->chan);
|
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?
|
//TODO: report unused fields as changed?
|
||||||
changed->clear();
|
changed->clear();
|
||||||
|
|||||||
+2
-2
@@ -16,8 +16,6 @@
|
|||||||
#include "pvif.h"
|
#include "pvif.h"
|
||||||
#include "pdb.h"
|
#include "pdb.h"
|
||||||
|
|
||||||
#include <shareLib.h>
|
|
||||||
|
|
||||||
struct PDBSingleMonitor;
|
struct PDBSingleMonitor;
|
||||||
|
|
||||||
struct QSRV_API PDBSinglePV : public PDBPV
|
struct QSRV_API PDBSinglePV : public PDBPV
|
||||||
@@ -32,6 +30,8 @@ struct QSRV_API PDBSinglePV : public PDBPV
|
|||||||
* is locked.
|
* is locked.
|
||||||
*/
|
*/
|
||||||
DBCH chan;
|
DBCH chan;
|
||||||
|
// used for DBE_PROPERTY subscription when chan has filters
|
||||||
|
DBCH chan2;
|
||||||
PDBProvider::shared_pointer provider;
|
PDBProvider::shared_pointer provider;
|
||||||
|
|
||||||
// only for use in pdb_single_event()
|
// only for use in pdb_single_event()
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ QSRV_API unsigned qsrvVersion(void);
|
|||||||
/** returns QSRV_ABI_VERSION_INT captured at compilation time */
|
/** returns QSRV_ABI_VERSION_INT captured at compilation time */
|
||||||
QSRV_API unsigned qsrvABIVersion(void);
|
QSRV_API unsigned qsrvABIVersion(void);
|
||||||
|
|
||||||
|
QSRV_API
|
||||||
|
long dbLoadGroup(const char* fname);
|
||||||
|
|
||||||
QSRV_API void testqsrvWaitForLinkEvent(struct link *plink);
|
QSRV_API void testqsrvWaitForLinkEvent(struct link *plink);
|
||||||
|
|
||||||
/** Call before testIocShutdownOk()
|
/** Call before testIocShutdownOk()
|
||||||
|
|||||||
+19
-2
@@ -33,6 +33,10 @@
|
|||||||
|
|
||||||
#include <epicsExport.h> /* defines epicsExportSharedSymbols */
|
#include <epicsExport.h> /* defines epicsExportSharedSymbols */
|
||||||
|
|
||||||
|
#if EPICS_VERSION_INT>=VERSION_INT(7,0,6,0)
|
||||||
|
# define HAVE_SHUTDOWN_HOOKS
|
||||||
|
#endif
|
||||||
|
|
||||||
int pvaLinkDebug;
|
int pvaLinkDebug;
|
||||||
int pvaLinkIsolate;
|
int pvaLinkIsolate;
|
||||||
|
|
||||||
@@ -67,6 +71,7 @@ static void shutdownStep2()
|
|||||||
pvaGlobal = NULL;
|
pvaGlobal = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef HAVE_SHUTDOWN_HOOKS
|
||||||
static void stopPVAPool(void*)
|
static void stopPVAPool(void*)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -84,8 +89,7 @@ static void finalizePVA(void*)
|
|||||||
fprintf(stderr, "Error initializing pva link handling : %s\n", e.what());
|
fprintf(stderr, "Error initializing pva link handling : %s\n", e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
bool atexitInstalled;
|
|
||||||
|
|
||||||
/* The Initialization game...
|
/* The Initialization game...
|
||||||
*
|
*
|
||||||
@@ -110,10 +114,13 @@ void initPVALink(initHookState state)
|
|||||||
}
|
}
|
||||||
pvaGlobal = new pvaGlobal_t;
|
pvaGlobal = new pvaGlobal_t;
|
||||||
|
|
||||||
|
#ifndef HAVE_SHUTDOWN_HOOKS
|
||||||
|
static bool atexitInstalled;
|
||||||
if(!atexitInstalled) {
|
if(!atexitInstalled) {
|
||||||
epicsAtExit(finalizePVA, NULL);
|
epicsAtExit(finalizePVA, NULL);
|
||||||
atexitInstalled = true;
|
atexitInstalled = true;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} else if(state==initHookAfterInitDatabase) {
|
} else if(state==initHookAfterInitDatabase) {
|
||||||
pvac::ClientProvider local("server:QSRV"),
|
pvac::ClientProvider local("server:QSRV"),
|
||||||
@@ -124,7 +131,10 @@ void initPVALink(initHookState state)
|
|||||||
} else if(state==initHookAfterIocBuilt) {
|
} else if(state==initHookAfterIocBuilt) {
|
||||||
// after epicsExit(exitDatabase)
|
// after epicsExit(exitDatabase)
|
||||||
// so hook registered here will be run before iocShutdown()
|
// so hook registered here will be run before iocShutdown()
|
||||||
|
|
||||||
|
#ifndef HAVE_SHUTDOWN_HOOKS
|
||||||
epicsAtExit(stopPVAPool, NULL);
|
epicsAtExit(stopPVAPool, NULL);
|
||||||
|
#endif
|
||||||
|
|
||||||
Guard G(pvaGlobal->lock);
|
Guard G(pvaGlobal->lock);
|
||||||
pvaGlobal->running = true;
|
pvaGlobal->running = true;
|
||||||
@@ -137,6 +147,13 @@ void initPVALink(initHookState state)
|
|||||||
|
|
||||||
chan->open();
|
chan->open();
|
||||||
}
|
}
|
||||||
|
#ifdef HAVE_SHUTDOWN_HOOKS
|
||||||
|
} else if(state==initHookAtShutdown) {
|
||||||
|
shutdownStep1();
|
||||||
|
|
||||||
|
} else if(state==initHookAfterShutdown) {
|
||||||
|
shutdownStep2();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}catch(std::exception& e){
|
}catch(std::exception& e){
|
||||||
cantProceed("Error initializing pva link handling : %s\n", e.what());
|
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 queued; // added to WorkQueue
|
||||||
bool debug; // set if any jlink::debug is set
|
bool debug; // set if any jlink::debug is set
|
||||||
std::tr1::shared_ptr<const void> previous_root;
|
std::tr1::shared_ptr<const void> previous_root;
|
||||||
|
typedef std::set<dbCommon*> after_put_t;
|
||||||
|
after_put_t after_put;
|
||||||
|
|
||||||
struct LinkSort {
|
struct LinkSort {
|
||||||
bool operator()(const pvaLink *L, const pvaLink *R) const;
|
bool operator()(const pvaLink *L, const pvaLink *R) const;
|
||||||
@@ -180,6 +182,12 @@ struct pvaLinkChannel : public pvac::ClientChannel::MonitorCallback,
|
|||||||
// pvac::ClientChanel::PutCallback
|
// pvac::ClientChanel::PutCallback
|
||||||
virtual void putBuild(const epics::pvData::StructureConstPtr& build, pvac::ClientChannel::PutCallback::Args& args) OVERRIDE FINAL;
|
virtual void putBuild(const epics::pvData::StructureConstPtr& build, pvac::ClientChannel::PutCallback::Args& args) OVERRIDE FINAL;
|
||||||
virtual void putDone(const pvac::PutEvent& evt) 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:
|
private:
|
||||||
virtual void run() OVERRIDE FINAL;
|
virtual void run() OVERRIDE FINAL;
|
||||||
void run_dbProcess(size_t idx); // idx is index in scan_records
|
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;
|
static size_t num_instances;
|
||||||
|
|
||||||
bool alive; // attempt to catch some use after free
|
bool alive; // attempt to catch some use after free
|
||||||
|
dbfType type;
|
||||||
|
|
||||||
DBLINK * plink; // may be NULL
|
DBLINK * plink; // may be NULL
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ pvaLinkChannel::pvaLinkChannel(const pvaGlobal_t::channels_key_t &key, const pvd
|
|||||||
,queued(false)
|
,queued(false)
|
||||||
,debug(false)
|
,debug(false)
|
||||||
,links_changed(false)
|
,links_changed(false)
|
||||||
|
,AP(new AfterPut)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
pvaLinkChannel::~pvaLinkChannel() {
|
pvaLinkChannel::~pvaLinkChannel() {
|
||||||
@@ -103,7 +104,7 @@ pvd::StructureConstPtr putRequestType = pvd::getFieldCreate()->createFieldBuilde
|
|||||||
void pvaLinkChannel::put(bool force)
|
void pvaLinkChannel::put(bool force)
|
||||||
{
|
{
|
||||||
pvd::PVStructurePtr pvReq(pvd::getPVDataCreate()->createPVStructure(putRequestType));
|
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;
|
unsigned reqProcess = 0;
|
||||||
bool doit = force;
|
bool doit = force;
|
||||||
@@ -191,22 +192,70 @@ void pvaLinkChannel::putBuild(const epics::pvData::StructureConstPtr& build, pva
|
|||||||
args.root = top;
|
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)
|
void pvaLinkChannel::putDone(const pvac::PutEvent& evt)
|
||||||
{
|
{
|
||||||
if(evt.event==pvac::PutEvent::Fail) {
|
if(evt.event==pvac::PutEvent::Fail) {
|
||||||
errlogPrintf("%s PVA link put ERROR: %s\n", key.first.c_str(), evt.message.c_str());
|
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) {
|
if(evt.event==pvac::PutEvent::Success) {
|
||||||
// see if we need start a queue'd put
|
// see if we need start a queue'd put
|
||||||
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)
|
void pvaLinkChannel::monitorEvent(const pvac::MonitorEvent& evt)
|
||||||
@@ -335,6 +384,10 @@ void pvaLinkChannel::run()
|
|||||||
|
|
||||||
if(!link->plink) continue;
|
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
|
// NPP and none/Default don't scan
|
||||||
// PP, CP, and CPP do scan
|
// PP, CP, and CPP do scan
|
||||||
// PP and CPP only if SCAN=Passive
|
// PP and CPP only if SCAN=Passive
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace pvalink {
|
|||||||
|
|
||||||
pvaLink::pvaLink()
|
pvaLink::pvaLink()
|
||||||
:alive(true)
|
:alive(true)
|
||||||
|
,type((dbfType)-1)
|
||||||
,plink(0)
|
,plink(0)
|
||||||
,used_scratch(false)
|
,used_scratch(false)
|
||||||
,used_queue(false)
|
,used_queue(false)
|
||||||
@@ -89,7 +90,9 @@ pvd::PVField::const_shared_pointer pvaLink::getSubField(const char *name)
|
|||||||
} else {
|
} else {
|
||||||
// we access a sub-struct
|
// we access a sub-struct
|
||||||
ret = lchan->op_mon.root->getSubField(fieldName);
|
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
|
// addressed sub-field isn't a sub-structure
|
||||||
if(strcmp(name, "value")!=0) {
|
if(strcmp(name, "value")!=0) {
|
||||||
// unless we are trying to fetch the "value", we fail here
|
// unless we are trying to fetch the "value", we fail here
|
||||||
|
|||||||
+34
-3
@@ -20,10 +20,23 @@ using namespace pvalink;
|
|||||||
|
|
||||||
#define CHECK_VALID() if(!self->valid()) { DEBUG(self, <<CURRENT_FUNCTION<<" "<<self->channelName<<" !valid"); return -1;}
|
#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)
|
void pvaOpenLink(DBLINK *plink)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
pvaLink* self((pvaLink*)plink->value.json.jlink);
|
pvaLink* self((pvaLink*)plink->value.json.jlink);
|
||||||
|
self->type = getLinkType(plink);
|
||||||
|
|
||||||
// workaround for Base not propagating info(base:lsetDebug to us
|
// workaround for Base not propagating info(base:lsetDebug to us
|
||||||
{
|
{
|
||||||
@@ -70,6 +83,7 @@ void pvaOpenLink(DBLINK *plink)
|
|||||||
// open new channel
|
// open new channel
|
||||||
|
|
||||||
chan.reset(new pvaLinkChannel(key, pvRequest));
|
chan.reset(new pvaLinkChannel(key, pvRequest));
|
||||||
|
chan->AP->lc = chan;
|
||||||
pvaGlobal->channels.insert(std::make_pair(key, chan));
|
pvaGlobal->channels.insert(std::make_pair(key, chan));
|
||||||
doOpen = true;
|
doOpen = true;
|
||||||
}
|
}
|
||||||
@@ -397,8 +411,8 @@ pvd::ScalarType DBR2PVD(short dbr)
|
|||||||
throw std::invalid_argument("Unsupported DBR code");
|
throw std::invalid_argument("Unsupported DBR code");
|
||||||
}
|
}
|
||||||
|
|
||||||
long pvaPutValue(DBLINK *plink, short dbrType,
|
long pvaPutValueX(DBLINK *plink, short dbrType,
|
||||||
const void *pbuffer, long nRequest)
|
const void *pbuffer, long nRequest, bool wait)
|
||||||
{
|
{
|
||||||
TRY {
|
TRY {
|
||||||
(void)self;
|
(void)self;
|
||||||
@@ -437,6 +451,11 @@ long pvaPutValue(DBLINK *plink, short dbrType,
|
|||||||
|
|
||||||
self->used_scratch = true;
|
self->used_scratch = true;
|
||||||
|
|
||||||
|
#ifdef USE_MULTILOCK
|
||||||
|
if(wait)
|
||||||
|
self->lchan->after_put.insert(plink->precord);
|
||||||
|
#endif
|
||||||
|
|
||||||
if(!self->defer) self->lchan->put();
|
if(!self->defer) self->lchan->put();
|
||||||
|
|
||||||
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<self->lchan->op_put.valid());
|
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;
|
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)
|
void pvaScanForward(DBLINK *plink)
|
||||||
{
|
{
|
||||||
TRY {
|
TRY {
|
||||||
@@ -485,7 +516,7 @@ lset pva_lset = {
|
|||||||
&pvaGetAlarm,
|
&pvaGetAlarm,
|
||||||
&pvaGetTimeStamp,
|
&pvaGetTimeStamp,
|
||||||
&pvaPutValue,
|
&pvaPutValue,
|
||||||
NULL,
|
&pvaPutValueAsync,
|
||||||
&pvaScanForward
|
&pvaScanForward
|
||||||
//&pvaReportLink,
|
//&pvaReportLink,
|
||||||
};
|
};
|
||||||
|
|||||||
+142
-45
@@ -40,19 +40,23 @@ namespace pva = epics::pvAccess;
|
|||||||
|
|
||||||
DBCH::DBCH(dbChannel *ch) :chan(ch)
|
DBCH::DBCH(dbChannel *ch) :chan(ch)
|
||||||
{
|
{
|
||||||
|
if(!chan)
|
||||||
|
throw std::invalid_argument("NULL channel");
|
||||||
prepare();
|
prepare();
|
||||||
}
|
}
|
||||||
|
|
||||||
DBCH::DBCH(const std::string& name)
|
DBCH::DBCH(const std::string& name)
|
||||||
:chan(dbChannelCreate(name.c_str()))
|
:chan(dbChannelCreate(name.c_str()))
|
||||||
{
|
{
|
||||||
|
if(!chan)
|
||||||
|
throw std::invalid_argument(SB()<<"invalid channel: "<<name);
|
||||||
prepare();
|
prepare();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DBCH::prepare()
|
void DBCH::prepare()
|
||||||
{
|
{
|
||||||
if(!chan)
|
if(!chan)
|
||||||
throw std::invalid_argument("NULL channel");
|
throw std::invalid_argument(SB()<<"NULL channel");
|
||||||
if(dbChannelOpen(chan)) {
|
if(dbChannelOpen(chan)) {
|
||||||
dbChannelDelete(chan);
|
dbChannelDelete(chan);
|
||||||
throw std::invalid_argument(SB()<<"Failed to open channel "<<dbChannelName(chan));
|
throw std::invalid_argument(SB()<<"Failed to open channel "<<dbChannelName(chan));
|
||||||
@@ -205,16 +209,20 @@ struct pvArray : public pvCommon {
|
|||||||
|
|
||||||
struct metaTIME {
|
struct metaTIME {
|
||||||
DBRstatus
|
DBRstatus
|
||||||
|
DBRamsg
|
||||||
DBRtime
|
DBRtime
|
||||||
|
DBRutag
|
||||||
|
|
||||||
enum {mask = DBR_STATUS | DBR_TIME};
|
enum {mask = DBR_STATUS | DBR_AMSG | DBR_TIME | DBR_UTAG};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct metaDOUBLE {
|
struct metaDOUBLE {
|
||||||
DBRstatus
|
DBRstatus
|
||||||
|
DBRamsg
|
||||||
DBRunits
|
DBRunits
|
||||||
DBRprecision
|
DBRprecision
|
||||||
DBRtime
|
DBRtime
|
||||||
|
DBRutag
|
||||||
DBRgrDouble
|
DBRgrDouble
|
||||||
DBRctrlDouble
|
DBRctrlDouble
|
||||||
DBRalDouble
|
DBRalDouble
|
||||||
@@ -222,12 +230,14 @@ struct metaDOUBLE {
|
|||||||
// similar junk
|
// similar junk
|
||||||
DBRenumStrs
|
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 {
|
struct metaENUM {
|
||||||
DBRstatus
|
DBRstatus
|
||||||
|
DBRamsg
|
||||||
DBRtime
|
DBRtime
|
||||||
|
DBRutag
|
||||||
DBRenumStrs
|
DBRenumStrs
|
||||||
|
|
||||||
// similar junk
|
// similar junk
|
||||||
@@ -237,12 +247,14 @@ struct metaENUM {
|
|||||||
DBRctrlDouble
|
DBRctrlDouble
|
||||||
DBRalDouble
|
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 {
|
struct metaSTRING {
|
||||||
DBRstatus
|
DBRstatus
|
||||||
|
DBRamsg
|
||||||
DBRtime
|
DBRtime
|
||||||
|
DBRutag
|
||||||
|
|
||||||
// similar junk
|
// similar junk
|
||||||
DBRenumStrs
|
DBRenumStrs
|
||||||
@@ -252,7 +264,7 @@ struct metaSTRING {
|
|||||||
DBRctrlDouble
|
DBRctrlDouble
|
||||||
DBRalDouble
|
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)
|
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(message, PVString, "alarm.message", ALARM);
|
||||||
FMAP(sec, PVLong, "timeStamp.secondsPastEpoch", ALWAYS);
|
FMAP(sec, PVLong, "timeStamp.secondsPastEpoch", ALWAYS);
|
||||||
FMAP(nsec, PVInt, "timeStamp.nanoseconds", ALWAYS);
|
FMAP(nsec, PVInt, "timeStamp.nanoseconds", ALWAYS);
|
||||||
|
#ifdef HAVE_UTAG
|
||||||
|
FMAP(userTag, PVInt, "timeStamp.userTag", ALWAYS);
|
||||||
|
#endif
|
||||||
#undef FMAP
|
#undef FMAP
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,16 +343,22 @@ void attachAll(PVX& pvm, const pvd::PVStructurePtr& pv)
|
|||||||
attachMeta(pvm, 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)
|
#ifdef HAVE_UTAG
|
||||||
message->put(epicsAlarmConditionStrings[code]);
|
if(meta.amsg[0]!='\0') {
|
||||||
|
message->put(meta.amsg);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
if(meta.status<ALARM_NSTATUS)
|
||||||
|
message->put(epicsAlarmConditionStrings[meta.status]);
|
||||||
else
|
else
|
||||||
message->put("???");
|
message->put("???");
|
||||||
|
|
||||||
// Arbitrary mapping from DB status codes
|
// Arbitrary mapping from DB status codes
|
||||||
unsigned out;
|
unsigned out;
|
||||||
switch(code) {
|
switch(meta.status) {
|
||||||
case NO_ALARM:
|
case NO_ALARM:
|
||||||
out = 0;
|
out = 0;
|
||||||
break;
|
break;
|
||||||
@@ -385,6 +406,10 @@ void putMetaImpl(const pvTimeAlarm& pv, const META& meta)
|
|||||||
if(pv.nsecMask) {
|
if(pv.nsecMask) {
|
||||||
pv.userTag->put(nsec&pv.nsecMask);
|
pv.userTag->put(nsec&pv.nsecMask);
|
||||||
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);
|
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);
|
putMetaImpl(pv, meta);
|
||||||
if(dbe&DBE_ALARM) {
|
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);
|
pv.severity->put(meta.severity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -546,7 +571,7 @@ void putMeta(const pvCommon& pv, unsigned dbe, db_field_log *pfl)
|
|||||||
putMetaImpl(pv, meta);
|
putMetaImpl(pv, meta);
|
||||||
#define FMAP(MNAME, FNAME) pv.MNAME->put(meta.FNAME)
|
#define FMAP(MNAME, FNAME) pv.MNAME->put(meta.FNAME)
|
||||||
if(dbe&DBE_ALARM) {
|
if(dbe&DBE_ALARM) {
|
||||||
mapStatus(meta.status, pv.status.get(), pv.message.get());
|
mapStatus(meta, pv.status.get(), pv.message.get());
|
||||||
FMAP(severity, severity);
|
FMAP(severity, severity);
|
||||||
}
|
}
|
||||||
if(dbe&DBE_PROPERTY) {
|
if(dbe&DBE_PROPERTY) {
|
||||||
@@ -641,6 +666,14 @@ void findFormat(pvTimeAlarm& pvmeta, pdbRecordIterator& info, const epics::pvDat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pvd::Status checkDISP(dbChannel *chan)
|
||||||
|
{
|
||||||
|
dbCommon *prec = dbChannelRecord(chan);
|
||||||
|
pvd::Status ret;
|
||||||
|
if(prec->disp && dbChannelField(chan)!=&prec->disp)
|
||||||
|
ret = pvd::Status::error("Put Disabled");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
template<typename PVX, typename META>
|
template<typename PVX, typename META>
|
||||||
struct PVIFScalarNumeric : public PVIF
|
struct PVIFScalarNumeric : public PVIF
|
||||||
@@ -698,7 +731,10 @@ struct PVIFScalarNumeric : public PVIF
|
|||||||
|
|
||||||
virtual pvd::Status get(const epics::pvData::BitSet& mask, proc_t proc, bool permit) OVERRIDE FINAL
|
virtual pvd::Status get(const epics::pvData::BitSet& mask, proc_t proc, bool permit) OVERRIDE FINAL
|
||||||
{
|
{
|
||||||
pvd::Status ret;
|
pvd::Status ret = checkDISP(chan);
|
||||||
|
if(!ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
bool newval = mask.logical_and(pvmeta.maskVALUEPut);
|
bool newval = mask.logical_and(pvmeta.maskVALUEPut);
|
||||||
if(newval) {
|
if(newval) {
|
||||||
if(permit)
|
if(permit)
|
||||||
@@ -763,9 +799,22 @@ short PVD2DBR(pvd::ScalarType pvt)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
epics::pvData::FieldConstPtr
|
static
|
||||||
ScalarBuilder::dtype(dbChannel *channel)
|
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);
|
short dbr = dbChannelFinalFieldType(channel);
|
||||||
const long maxelem = dbChannelFinalElements(channel);
|
const long maxelem = dbChannelFinalElements(channel);
|
||||||
const pvd::ScalarType pvt = DBR2PVD(dbr);
|
const pvd::ScalarType pvt = DBR2PVD(dbr);
|
||||||
@@ -794,7 +843,7 @@ ScalarBuilder::dtype(dbChannel *channel)
|
|||||||
->addArray("value", pvt);
|
->addArray("value", pvt);
|
||||||
|
|
||||||
builder = builder->add("alarm", standard->alarm())
|
builder = builder->add("alarm", standard->alarm())
|
||||||
->add("timeStamp", standard->timeStamp());
|
->add("timeStamp", buildTimeStamp());
|
||||||
|
|
||||||
if(dbr!=DBR_ENUM) {
|
if(dbr!=DBR_ENUM) {
|
||||||
builder = builder->addNestedStructure("display")
|
builder = builder->addNestedStructure("display")
|
||||||
@@ -819,7 +868,7 @@ ScalarBuilder::dtype(dbChannel *channel)
|
|||||||
}
|
}
|
||||||
|
|
||||||
PVIF*
|
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)
|
if(!channel)
|
||||||
throw std::runtime_error("+type:\"scalar\" requires +channel:");
|
throw std::runtime_error("+type:\"scalar\" requires +channel:");
|
||||||
@@ -906,7 +955,10 @@ struct PVIFPlain : public PVIF
|
|||||||
|
|
||||||
virtual pvd::Status get(const epics::pvData::BitSet& mask, proc_t proc, bool permit) OVERRIDE FINAL
|
virtual pvd::Status get(const epics::pvData::BitSet& mask, proc_t proc, bool permit) OVERRIDE FINAL
|
||||||
{
|
{
|
||||||
pvd::Status ret;
|
pvd::Status ret = checkDISP(chan);
|
||||||
|
if(!ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
bool newval = mask.get(fieldOffset);
|
bool newval = mask.get(fieldOffset);
|
||||||
if(newval) {
|
if(newval) {
|
||||||
if(permit)
|
if(permit)
|
||||||
@@ -936,10 +988,14 @@ struct PVIFPlain : public PVIF
|
|||||||
|
|
||||||
struct PlainBuilder : public PVIFBuilder
|
struct PlainBuilder : public PVIFBuilder
|
||||||
{
|
{
|
||||||
|
explicit PlainBuilder(dbChannel* chan) :PVIFBuilder(chan) {}
|
||||||
virtual ~PlainBuilder() {}
|
virtual ~PlainBuilder() {}
|
||||||
|
|
||||||
// fetch the structure description
|
// 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 short dbr = dbChannelFinalFieldType(channel);
|
||||||
const long maxelem = dbChannelFinalElements(channel);
|
const long maxelem = dbChannelFinalElements(channel);
|
||||||
const pvd::ScalarType pvt = DBR2PVD(dbr);
|
const pvd::ScalarType pvt = DBR2PVD(dbr);
|
||||||
@@ -956,8 +1012,7 @@ struct PlainBuilder : public PVIFBuilder
|
|||||||
// Attach to a structure instance.
|
// Attach to a structure instance.
|
||||||
// must be of the type returned by dtype().
|
// must be of the type returned by dtype().
|
||||||
// need not be the root structure
|
// need not be the root structure
|
||||||
virtual PVIF* attach(dbChannel *channel,
|
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root,
|
||||||
const epics::pvData::PVStructurePtr& root,
|
|
||||||
const FieldName& fldname) OVERRIDE FINAL
|
const FieldName& fldname) OVERRIDE FINAL
|
||||||
{
|
{
|
||||||
if(!channel)
|
if(!channel)
|
||||||
@@ -976,10 +1031,11 @@ struct PlainBuilder : public PVIFBuilder
|
|||||||
|
|
||||||
struct AnyScalarBuilder : public PVIFBuilder
|
struct AnyScalarBuilder : public PVIFBuilder
|
||||||
{
|
{
|
||||||
|
explicit AnyScalarBuilder(dbChannel* chan) :PVIFBuilder(chan) {}
|
||||||
virtual ~AnyScalarBuilder() {}
|
virtual ~AnyScalarBuilder() {}
|
||||||
|
|
||||||
// fetch the structure description
|
// fetch the structure description
|
||||||
virtual epics::pvData::FieldConstPtr dtype(dbChannel *channel) OVERRIDE FINAL {
|
virtual epics::pvData::FieldConstPtr dtype() OVERRIDE FINAL {
|
||||||
(void)channel; //ignored
|
(void)channel; //ignored
|
||||||
return pvd::getFieldCreate()->createVariantUnion();
|
return pvd::getFieldCreate()->createVariantUnion();
|
||||||
}
|
}
|
||||||
@@ -987,8 +1043,7 @@ struct AnyScalarBuilder : public PVIFBuilder
|
|||||||
// Attach to a structure instance.
|
// Attach to a structure instance.
|
||||||
// must be of the type returned by dtype().
|
// must be of the type returned by dtype().
|
||||||
// need not be the root structure
|
// need not be the root structure
|
||||||
virtual PVIF* attach(dbChannel *channel,
|
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root,
|
||||||
const epics::pvData::PVStructurePtr& root,
|
|
||||||
const FieldName& fldname) OVERRIDE FINAL
|
const FieldName& fldname) OVERRIDE FINAL
|
||||||
{
|
{
|
||||||
if(!channel)
|
if(!channel)
|
||||||
@@ -1074,25 +1129,25 @@ struct PVIFMeta : public PVIF
|
|||||||
|
|
||||||
struct MetaBuilder : public PVIFBuilder
|
struct MetaBuilder : public PVIFBuilder
|
||||||
{
|
{
|
||||||
|
explicit MetaBuilder(dbChannel* chan) :PVIFBuilder(chan) {}
|
||||||
virtual ~MetaBuilder() {}
|
virtual ~MetaBuilder() {}
|
||||||
|
|
||||||
// fetch the structure description
|
// 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");
|
throw std::logic_error("Don't call me");
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual epics::pvData::FieldBuilderPtr dtype(epics::pvData::FieldBuilderPtr& builder,
|
virtual epics::pvData::FieldBuilderPtr dtype(epics::pvData::FieldBuilderPtr& builder,
|
||||||
const std::string& fld,
|
const std::string& fld) OVERRIDE FINAL
|
||||||
dbChannel *channel)
|
|
||||||
{
|
{
|
||||||
pvd::StandardFieldPtr std(pvd::getStandardField());
|
pvd::StandardFieldPtr std(pvd::getStandardField());
|
||||||
if(fld.empty()) {
|
if(fld.empty()) {
|
||||||
return builder->add("alarm", std->alarm())
|
return builder->add("alarm", std->alarm())
|
||||||
->add("timeStamp", std->timeStamp());
|
->add("timeStamp", buildTimeStamp());
|
||||||
} else {
|
} else {
|
||||||
return builder->addNestedStructure(fld)
|
return builder->addNestedStructure(fld)
|
||||||
->add("alarm", std->alarm())
|
->add("alarm", std->alarm())
|
||||||
->add("timeStamp", std->timeStamp())
|
->add("timeStamp", buildTimeStamp())
|
||||||
->endNested();
|
->endNested();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1100,8 +1155,7 @@ struct MetaBuilder : public PVIFBuilder
|
|||||||
// Attach to a structure instance.
|
// Attach to a structure instance.
|
||||||
// must be of the type returned by dtype().
|
// must be of the type returned by dtype().
|
||||||
// need not be the root structure
|
// need not be the root structure
|
||||||
virtual PVIF* attach(dbChannel *channel,
|
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root,
|
||||||
const epics::pvData::PVStructurePtr& root,
|
|
||||||
const FieldName& fldname) OVERRIDE FINAL
|
const FieldName& fldname) OVERRIDE FINAL
|
||||||
{
|
{
|
||||||
if(!channel)
|
if(!channel)
|
||||||
@@ -1138,20 +1192,21 @@ struct PVIFProc : public PVIF
|
|||||||
|
|
||||||
struct ProcBuilder : public PVIFBuilder
|
struct ProcBuilder : public PVIFBuilder
|
||||||
{
|
{
|
||||||
|
explicit ProcBuilder(dbChannel* chan) :PVIFBuilder(chan) {}
|
||||||
|
virtual ~ProcBuilder() {}
|
||||||
|
|
||||||
// fetch the structure description
|
// 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");
|
throw std::logic_error("Don't call me");
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual epics::pvData::FieldBuilderPtr dtype(epics::pvData::FieldBuilderPtr& builder,
|
virtual epics::pvData::FieldBuilderPtr dtype(epics::pvData::FieldBuilderPtr& builder,
|
||||||
const std::string& fld,
|
const std::string& fld) OVERRIDE FINAL
|
||||||
dbChannel *channel) OVERRIDE FINAL
|
|
||||||
{
|
{
|
||||||
// invisible
|
// invisible
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
virtual PVIF* attach(dbChannel *channel,
|
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root,
|
||||||
const epics::pvData::PVStructurePtr& root,
|
|
||||||
const FieldName& fldname) OVERRIDE FINAL
|
const FieldName& fldname) OVERRIDE FINAL
|
||||||
{
|
{
|
||||||
if(!channel)
|
if(!channel)
|
||||||
@@ -1161,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
|
}//namespace
|
||||||
|
|
||||||
pvd::Status PVIF::get(const epics::pvData::BitSet& mask, proc_t proc, bool permit)
|
pvd::Status PVIF::get(const epics::pvData::BitSet& mask, proc_t proc, bool permit)
|
||||||
@@ -1202,31 +1298,32 @@ pvd::Status PVIF::get(const epics::pvData::BitSet& mask, proc_t proc, bool permi
|
|||||||
|
|
||||||
epics::pvData::FieldBuilderPtr
|
epics::pvData::FieldBuilderPtr
|
||||||
PVIFBuilder::dtype(epics::pvData::FieldBuilderPtr& builder,
|
PVIFBuilder::dtype(epics::pvData::FieldBuilderPtr& builder,
|
||||||
const std::string &fld,
|
const std::string &fld)
|
||||||
dbChannel *channel)
|
|
||||||
{
|
{
|
||||||
if(fld.empty())
|
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)
|
if(ftype)
|
||||||
builder = builder->add(fld, ftype);
|
builder = builder->add(fld, ftype);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
PVIFBuilder* PVIFBuilder::create(const std::string& type)
|
PVIFBuilder* PVIFBuilder::create(const std::string& type, dbChannel* chan)
|
||||||
{
|
{
|
||||||
if(type.empty() || type=="scalar")
|
if(type.empty() || type=="scalar")
|
||||||
return new ScalarBuilder;
|
return new ScalarBuilder(chan);
|
||||||
else if(type=="plain")
|
else if(type=="plain")
|
||||||
return new PlainBuilder;
|
return new PlainBuilder(chan);
|
||||||
else if(type=="any")
|
else if(type=="any")
|
||||||
return new AnyScalarBuilder;
|
return new AnyScalarBuilder(chan);
|
||||||
else if(type=="meta")
|
else if(type=="meta")
|
||||||
return new MetaBuilder;
|
return new MetaBuilder(chan);
|
||||||
else if(type=="proc")
|
else if(type=="proc")
|
||||||
return new ProcBuilder;
|
return new ProcBuilder(chan);
|
||||||
|
else if(type=="structure")
|
||||||
|
return new IDBuilder(chan);
|
||||||
else
|
else
|
||||||
throw std::runtime_error(std::string("Unknown +type=")+type);
|
throw std::runtime_error(std::string("Unknown +type=")+type);
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-10
@@ -30,6 +30,15 @@
|
|||||||
# define USE_MULTILOCK
|
# define USE_MULTILOCK
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef DBRutag
|
||||||
|
# define DBR_AMSG 0
|
||||||
|
# define DBR_UTAG 0
|
||||||
|
# define DBRamsg
|
||||||
|
# define DBRutag
|
||||||
|
#else
|
||||||
|
# define HAVE_UTAG
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace epics {
|
namespace epics {
|
||||||
namespace pvAccess {
|
namespace pvAccess {
|
||||||
class ChannelRequester;
|
class ChannelRequester;
|
||||||
@@ -246,7 +255,7 @@ struct LocalFL
|
|||||||
:pfl(pfl)
|
:pfl(pfl)
|
||||||
,ours(false)
|
,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);
|
pfl = db_create_read_log(pchan);
|
||||||
if(pfl) {
|
if(pfl) {
|
||||||
ours = true;
|
ours = true;
|
||||||
@@ -254,6 +263,7 @@ struct LocalFL
|
|||||||
if(pfl) pfl = dbChannelRunPostChain(pchan, pfl);
|
if(pfl) pfl = dbChannelRunPostChain(pchan, pfl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this->pfl = pfl;
|
||||||
}
|
}
|
||||||
~LocalFL() {
|
~LocalFL() {
|
||||||
if(ours) db_delete_field_log(pfl);
|
if(ours) db_delete_field_log(pfl);
|
||||||
@@ -378,25 +388,39 @@ private:
|
|||||||
PVIF& operator=(const PVIF&);
|
PVIF& operator=(const PVIF&);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct QSRV_API PVIFBuilder {
|
/** Factory for PVIF instances.
|
||||||
|
*
|
||||||
|
* Caller first passes a mapping type (eg. "scalar") to PVIFBuilder::create()
|
||||||
|
* to obtain a PVIFBuilder which may then by used to create PVIF instances
|
||||||
|
* for specific dbChannel.
|
||||||
|
*
|
||||||
|
* Caller than uses PVIFBuilder::dtype() to obtain (sub)Field descriptions.
|
||||||
|
* eg. more than one of these may be composed into an overall Structure description.
|
||||||
|
*
|
||||||
|
* Caller than creates a PVStructure and uses PVIFBuilder::attach() to
|
||||||
|
* build mappings for each dbChannel in the composed locations.
|
||||||
|
*/
|
||||||
|
struct QSRV_API PVIFBuilder
|
||||||
|
{
|
||||||
|
dbChannel* const channel;
|
||||||
|
|
||||||
virtual ~PVIFBuilder() {}
|
virtual ~PVIFBuilder() {}
|
||||||
|
|
||||||
// fetch the structure description
|
// 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,
|
virtual epics::pvData::FieldBuilderPtr dtype(epics::pvData::FieldBuilderPtr& builder,
|
||||||
const std::string& fld,
|
const std::string& fld);
|
||||||
dbChannel *channel);
|
|
||||||
|
|
||||||
// Attach to a structure instance.
|
// Attach to a structure instance.
|
||||||
// must be of the type returned by dtype().
|
// must be of the type returned by dtype().
|
||||||
// must be the root structure
|
// 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;
|
||||||
|
|
||||||
static PVIFBuilder* create(const std::string& name);
|
// entry point for Builder
|
||||||
|
static PVIFBuilder* create(const std::string& mapname, dbChannel* chan);
|
||||||
protected:
|
protected:
|
||||||
PVIFBuilder() {}
|
explicit PVIFBuilder(dbChannel* chan) : channel(chan) {}
|
||||||
private:
|
private:
|
||||||
PVIFBuilder(const PVIFBuilder&);
|
PVIFBuilder(const PVIFBuilder&);
|
||||||
PVIFBuilder& operator=(const PVIFBuilder&);
|
PVIFBuilder& operator=(const PVIFBuilder&);
|
||||||
@@ -404,10 +428,11 @@ private:
|
|||||||
|
|
||||||
struct QSRV_API ScalarBuilder : public PVIFBuilder
|
struct QSRV_API ScalarBuilder : public PVIFBuilder
|
||||||
{
|
{
|
||||||
|
explicit ScalarBuilder(dbChannel* chan) :PVIFBuilder(chan) {}
|
||||||
virtual ~ScalarBuilder() {}
|
virtual ~ScalarBuilder() {}
|
||||||
|
|
||||||
virtual epics::pvData::FieldConstPtr dtype(dbChannel *channel) OVERRIDE FINAL;
|
virtual epics::pvData::FieldConstPtr dtype() OVERRIDE FINAL;
|
||||||
virtual PVIF* attach(dbChannel *channel, const epics::pvData::PVStructurePtr& root, const FieldName& fld) OVERRIDE FINAL;
|
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root, const FieldName& fld) OVERRIDE FINAL;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ link("pva", "lsetPVA")
|
|||||||
|
|
||||||
# from demo.cpp
|
# from demo.cpp
|
||||||
device(waveform, CONSTANT, devWfPDBDemo, "QSRV Demo")
|
device(waveform, CONSTANT, devWfPDBDemo, "QSRV Demo")
|
||||||
|
device(waveform, CONSTANT, devWfPDBDemoRepeat, "QSRV Demo Replicate")
|
||||||
|
device(longin, CONSTANT, devLoPDBUTag, "QSRV Set UTag")
|
||||||
# from imagedemo.c
|
# from imagedemo.c
|
||||||
function(QSRV_image_demo)
|
function(QSRV_image_demo)
|
||||||
# from pdb.cpp
|
# from pdb.cpp
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ registrar(installPVAAddLinkHook)
|
|||||||
|
|
||||||
# from demo.cpp
|
# from demo.cpp
|
||||||
device(waveform, CONSTANT, devWfPDBDemo, "QSRV Demo")
|
device(waveform, CONSTANT, devWfPDBDemo, "QSRV Demo")
|
||||||
|
device(waveform, CONSTANT, devWfPDBDemoRepeat, "QSRV Demo Replicate")
|
||||||
|
device(longin, CONSTANT, devLoPDBUTag, "QSRV Set UTag")
|
||||||
# from imagedemo.c
|
# from imagedemo.c
|
||||||
function(QSRV_image_demo)
|
function(QSRV_image_demo)
|
||||||
# from pdb.cpp
|
# from pdb.cpp
|
||||||
|
|||||||
@@ -46,8 +46,49 @@ void QSRVRegistrar_counters()
|
|||||||
epics::registerRefCounter("PDBProvider", &PDBProvider::num_instances);
|
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 {
|
namespace {
|
||||||
|
|
||||||
|
void dbLoadGroupWrap(const char* fname)
|
||||||
|
{
|
||||||
|
(void)dbLoadGroup(fname);
|
||||||
|
}
|
||||||
|
|
||||||
void dbgl(int lvl, const char *pattern)
|
void dbgl(int lvl, const char *pattern)
|
||||||
{
|
{
|
||||||
if(!pattern)
|
if(!pattern)
|
||||||
@@ -88,6 +129,7 @@ void QSRVRegistrar()
|
|||||||
QSRVRegistrar_counters();
|
QSRVRegistrar_counters();
|
||||||
pva::ChannelProviderRegistry::servers()->addSingleton<PDBProvider>("QSRV");
|
pva::ChannelProviderRegistry::servers()->addSingleton<PDBProvider>("QSRV");
|
||||||
epics::iocshRegister<int, const char*, &dbgl>("dbgl", "level", "pattern");
|
epics::iocshRegister<int, const char*, &dbgl>("dbgl", "level", "pattern");
|
||||||
|
epics::iocshRegister<const char*, &dbLoadGroupWrap>("dbLoadGroup", "jsonfile");
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
+241
-207
@@ -7,242 +7,276 @@
|
|||||||
* found in the file LICENSE that is included with this distribution.
|
* found in the file LICENSE that is included with this distribution.
|
||||||
\*************************************************************************/
|
\*************************************************************************/
|
||||||
|
|
||||||
/* Copyed from EPICS Base 3.16 branch */
|
/* Author: Andrew Johnson Date: 2003-04-08 */
|
||||||
|
|
||||||
/* Author: Andrew Johnson Date: 2003-04-08 */
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
/* Usage:
|
#include <list>
|
||||||
* softIoc [-D softIoc.dbd] [-h] [-S] [-s] [-a ascf]
|
#include <stdexcept>
|
||||||
* [-m macro=value,macro2=value2] [-d file.db]
|
|
||||||
* [-x prefix] [st.cmd]
|
|
||||||
*
|
|
||||||
* If used the -D option must come first, and specify the
|
|
||||||
* path to the softIoc.dbd file. The compile-time install
|
|
||||||
* location is saved in the binary as a default.
|
|
||||||
*
|
|
||||||
* Usage information will be printed if -h is given, then
|
|
||||||
* the program will exit normally.
|
|
||||||
*
|
|
||||||
* The -S option prevents an interactive shell being started
|
|
||||||
* after all arguments have been processed.
|
|
||||||
*
|
|
||||||
* Previous versions accepted a -s option to cause a shell
|
|
||||||
* to be started; this option is still accepted but ignored
|
|
||||||
* since a command shell is now started by default.
|
|
||||||
*
|
|
||||||
* Access Security can be enabled with the -a option giving
|
|
||||||
* the name of the configuration file; if any macros were
|
|
||||||
* set with -m before the -a option was given, they will be
|
|
||||||
* used as access security substitution macros.
|
|
||||||
*
|
|
||||||
* Any number of -m and -d arguments can be interspersed;
|
|
||||||
* the macros are applied to the following .db files. Each
|
|
||||||
* later -m option causes earlier macros to be discarded.
|
|
||||||
*
|
|
||||||
* The -x option loads the softIocExit.db with the macro
|
|
||||||
* IOC set to the string provided. This database contains
|
|
||||||
* a subroutine record named $(IOC):exit which has its field
|
|
||||||
* SNAM set to "exit". When this record is processed, the
|
|
||||||
* subroutine that runs will call epicsExit() with the value
|
|
||||||
* of the field A determining whether the exit status is
|
|
||||||
* EXIT_SUCCESS if (A == 0.0) or EXIT_FAILURE (A != 0.0).
|
|
||||||
*
|
|
||||||
* A st.cmd file is optional. If any databases were loaded
|
|
||||||
* the st.cmd file will be run *after* iocInit. To perform
|
|
||||||
* iocsh commands before iocInit, all database loading must
|
|
||||||
* be performed by the script itself, or by the user from
|
|
||||||
* the interactive IOC shell.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
|
#include <epicsVersion.h>
|
||||||
|
#include <epicsGetopt.h>
|
||||||
#include "registryFunction.h"
|
#include "registryFunction.h"
|
||||||
#include "epicsThread.h"
|
#include "epicsThread.h"
|
||||||
#include "epicsExit.h"
|
#include "epicsExit.h"
|
||||||
#include "epicsStdio.h"
|
#include "epicsStdio.h"
|
||||||
|
#include "epicsString.h"
|
||||||
#include "dbStaticLib.h"
|
#include "dbStaticLib.h"
|
||||||
#include "subRecord.h"
|
#include "subRecord.h"
|
||||||
#include "dbAccess.h"
|
#include "dbAccess.h"
|
||||||
#include "asDbLib.h"
|
#include "asDbLib.h"
|
||||||
#include "iocInit.h"
|
#include "iocInit.h"
|
||||||
#include "iocsh.h"
|
#include "iocsh.h"
|
||||||
|
#include "osiFileName.h"
|
||||||
|
|
||||||
|
#include <pv/qsrv.h>
|
||||||
|
|
||||||
extern "C" int softIocPVA_registerRecordDeviceDriver(struct dbBase *pdbbase);
|
extern "C" int softIocPVA_registerRecordDeviceDriver(struct dbBase *pdbbase);
|
||||||
|
|
||||||
#ifdef __rtems__
|
#ifndef EPICS_BASE
|
||||||
#define DBD_FILE "dbd/softIocPVA.dbd"
|
// so IDEs knows EPICS_BASE is a string constant
|
||||||
#define EXIT_FILE "db/softIocExit.db"
|
# define EPICS_BASE "/"
|
||||||
#else
|
# error -DEPICS_BASE required
|
||||||
#define DBD_FILE FINAL_LOCATION "/dbd/softIocPVA.dbd"
|
|
||||||
#define EXIT_FILE FINAL_LOCATION "/db/softIocExit.db"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef VERSION_INT
|
#if EPICS_VERSION_INT>=VERSION_INT(7,0,2,0)
|
||||||
#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1)
|
# define USE_EXECDIR
|
||||||
#define USE_EXIT_LATER
|
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
const char *arg0;
|
|
||||||
const char *base_dbd = DBD_FILE;
|
|
||||||
const char *exit_db = EXIT_FILE;
|
|
||||||
|
|
||||||
|
#define DBD_BASE "dbd" OSI_PATH_SEPARATOR "softIocPVA.dbd"
|
||||||
|
#define EXIT_BASE "db" OSI_PATH_SEPARATOR "softIocExit.db"
|
||||||
|
#define DBD_FILE_REL ".." OSI_PATH_SEPARATOR ".." OSI_PATH_SEPARATOR DBD_BASE
|
||||||
|
#define EXIT_FILE_REL ".." OSI_PATH_SEPARATOR ".." OSI_PATH_SEPARATOR EXIT_BASE
|
||||||
|
#define DBD_FILE EPICS_BASE OSI_PATH_SEPARATOR DBD_BASE
|
||||||
|
#define EXIT_FILE EPICS_BASE OSI_PATH_SEPARATOR EXIT_BASE
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool verbose = false;
|
||||||
|
|
||||||
static void exitSubroutine(subRecord *precord) {
|
static void exitSubroutine(subRecord *precord) {
|
||||||
#ifdef USE_EXIT_LATER
|
|
||||||
epicsExitLater((precord->a == 0.0) ? EXIT_SUCCESS : EXIT_FAILURE);
|
epicsExitLater((precord->a == 0.0) ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||||
#else
|
|
||||||
epicsExit((precord->a == 0.0) ? EXIT_SUCCESS : EXIT_FAILURE);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void usage(int status) {
|
void usage(const char *arg0, const std::string& base_dbd) {
|
||||||
printf("Usage: %s [-D softIoc.dbd] [-h] [-S] [-a ascf]\n", arg0);
|
std::cout<<"Usage: "<<arg0<<
|
||||||
puts("\t[-m macro=value,macro2=value2] [-d file.db]");
|
" [-D softIocPVA.dbd] [-h] [-S] [-s] [-v] [-a ascf]\n"
|
||||||
puts("\t[-x prefix] [st.cmd]");
|
"[-m macro=value,macro2=value2] [-d file.db]\n"
|
||||||
puts("Compiled-in path to softIocPVA.dbd is:");
|
"[-x prefix] [st.cmd]\n"
|
||||||
printf("\t%s\n", base_dbd);
|
"\n"
|
||||||
epicsExit(status);
|
" -D <dbd> If used, must come first. Specify the path to the softIocPVA.dbdfile."
|
||||||
|
" The compile-time install location is saved in the binary as a default.\n"
|
||||||
|
"\n"
|
||||||
|
" -h Print this mesage and exit.\n"
|
||||||
|
"\n"
|
||||||
|
" -S Prevents an interactive shell being started.\n"
|
||||||
|
"\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"
|
||||||
|
" -d <db> Load records from file (dbLoadRecords). Macro substitution is\n"
|
||||||
|
" performed.\n"
|
||||||
|
"\n"
|
||||||
|
" -x <prefix> Load softIocExit.db. Provides a record \"<prefix>:exit\".\n"
|
||||||
|
" Put 0 to exit with success, or non-zero to exit with an error.\n"
|
||||||
|
"\n"
|
||||||
|
"Any number of -m and -d arguments can be interspersed; the macros are applied\n"
|
||||||
|
"to the following .db files. Each later -m option causes earlier macros to be\n"
|
||||||
|
"discarded.\n"
|
||||||
|
"\n"
|
||||||
|
"A st.cmd file is optional. If any databases were loaded the st.cmd file will\n"
|
||||||
|
"be run *after* iocInit. To perform iocsh commands before iocInit, all database\n"
|
||||||
|
"loading must be performed by the script itself, or by the user from the\n"
|
||||||
|
"interactive IOC shell.\n"
|
||||||
|
"\n"
|
||||||
|
"Compiled-in path to softIocPVA.dbd is:\n"
|
||||||
|
"\t"<<base_dbd.c_str()<<"\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void errIf(int ret, const std::string& msg)
|
||||||
|
{
|
||||||
|
if(ret)
|
||||||
|
throw std::runtime_error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lazy_dbd_loaded;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (verbose)
|
||||||
|
std::cout<<"softIocPVA_registerRecordDeviceDriver(pdbbase)\n";
|
||||||
|
softIocPVA_registerRecordDeviceDriver(pdbbase);
|
||||||
|
registryFunctionAdd("exit", (REGISTRYFUNCTION) exitSubroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
char *dbd_file = const_cast<char*>(base_dbd);
|
try {
|
||||||
char *macros = NULL;
|
std::string dbd_file(DBD_FILE),
|
||||||
char xmacro[PVNAME_STRINGSZ + 4];
|
exit_file(EXIT_FILE),
|
||||||
int startIocsh = 1; /* default = start shell */
|
macros, // scratch space for macros (may be given more than once)
|
||||||
int loadedDb = 0;
|
xmacro;
|
||||||
|
bool interactive = true;
|
||||||
|
bool loadedDb = false;
|
||||||
|
bool ranScript = false;
|
||||||
|
|
||||||
arg0 = strrchr(*argv, '/');
|
#ifdef USE_EXECDIR
|
||||||
if (!arg0) {
|
// attempt to compute relative paths
|
||||||
arg0 = *argv;
|
{
|
||||||
} else {
|
std::string prefix;
|
||||||
++arg0; /* skip the '/' */
|
char *cprefix = epicsGetExecDir();
|
||||||
|
if(cprefix) {
|
||||||
|
try {
|
||||||
|
prefix = cprefix;
|
||||||
|
free(cprefix);
|
||||||
|
} catch(...) {
|
||||||
|
free(cprefix);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbd_file = prefix + DBD_FILE_REL;
|
||||||
|
exit_file = prefix + EXIT_FILE_REL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int opt;
|
||||||
|
|
||||||
|
while ((opt = getopt(argc, argv, "ha:D:d:m:Ssx:G:v")) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'h': /* Print usage */
|
||||||
|
usage(argv[0], dbd_file);
|
||||||
|
epicsExit(0);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
usage(argv[0], dbd_file);
|
||||||
|
std::cerr<<"Unknown argument: -"<<char(opt)<<"\n";
|
||||||
|
epicsExit(2);
|
||||||
|
return 2;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
if (verbose)
|
||||||
|
std::cout<<"asSetFilename(\""<<optarg<<"\")\n";
|
||||||
|
if(asSetFilename(optarg))
|
||||||
|
throw std::bad_alloc();
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
if(lazy_dbd_loaded) {
|
||||||
|
throw std::runtime_error("-D specified too late. softIocPVA.dbd already loaded.\n");
|
||||||
|
}
|
||||||
|
dbd_file = optarg;
|
||||||
|
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);
|
||||||
|
loadedDb = true;
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
macros = optarg;
|
||||||
|
break;
|
||||||
|
case 'S':
|
||||||
|
interactive = false;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
break; // historical
|
||||||
|
case 'v':
|
||||||
|
verbose = true;
|
||||||
|
break;
|
||||||
|
case 'x':
|
||||||
|
lazy_dbd(dbd_file);
|
||||||
|
xmacro = "IOC=";
|
||||||
|
xmacro += optarg;
|
||||||
|
errIf(dbLoadRecords(exit_file.c_str(), xmacro.c_str()),
|
||||||
|
std::string("Failed to load: ")+exit_file);
|
||||||
|
loadedDb = true;
|
||||||
|
break;
|
||||||
|
case 'G':
|
||||||
|
dbLoadGroup(optarg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_dbd(dbd_file);
|
||||||
|
|
||||||
|
if(optind<argc) {
|
||||||
|
// run script
|
||||||
|
// ignore any extra positional args (historical)
|
||||||
|
|
||||||
|
if (verbose)
|
||||||
|
std::cout<<"# Begin "<<argv[optind]<<"\n";
|
||||||
|
errIf(iocsh(argv[optind]),
|
||||||
|
std::string("Error in ")+argv[optind]);
|
||||||
|
if (verbose)
|
||||||
|
std::cout<<"# End "<<argv[optind]<<"\n";
|
||||||
|
|
||||||
|
epicsThreadSleep(0.2);
|
||||||
|
ranScript = true; /* Assume the script has done any necessary initialization */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadedDb) {
|
||||||
|
if (verbose)
|
||||||
|
std::cout<<"iocInit()\n";
|
||||||
|
iocInit();
|
||||||
|
epicsThreadSleep(0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(interactive) {
|
||||||
|
std::cout.flush();
|
||||||
|
std::cerr.flush();
|
||||||
|
if(iocsh(NULL)) {
|
||||||
|
epicsExit(1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (loadedDb || ranScript) {
|
||||||
|
epicsThreadExitMain();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
usage(argv[0], dbd_file);
|
||||||
|
std::cerr<<"Nothing to do!\n";
|
||||||
|
epicsExit(1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
epicsExit(0);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}catch(std::exception& e){
|
||||||
|
std::cerr<<"Error: "<<e.what()<<"\n";
|
||||||
|
epicsExit(2);
|
||||||
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
--argc, ++argv;
|
|
||||||
|
|
||||||
/* Do this here in case the dbd file not available */
|
|
||||||
if (argc>0 && **argv=='-' && (*argv)[1]=='h') {
|
|
||||||
usage(EXIT_SUCCESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argc>1 && **argv=='-' && (*argv)[1]=='D') {
|
|
||||||
dbd_file = *++argv;
|
|
||||||
argc -= 2;
|
|
||||||
++argv;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dbLoadDatabase(dbd_file, NULL, NULL)) {
|
|
||||||
epicsExit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
softIocPVA_registerRecordDeviceDriver(pdbbase);
|
|
||||||
registryFunctionAdd("exit", (REGISTRYFUNCTION) exitSubroutine);
|
|
||||||
|
|
||||||
while (argc>1 && **argv == '-') {
|
|
||||||
switch ((*argv)[1]) {
|
|
||||||
case 'a':
|
|
||||||
if (macros) asSetSubstitutions(macros);
|
|
||||||
asSetFilename(*++argv);
|
|
||||||
--argc;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'd':
|
|
||||||
if (dbLoadRecords(*++argv, macros)) {
|
|
||||||
epicsExit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
loadedDb = 1;
|
|
||||||
--argc;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'h':
|
|
||||||
usage(EXIT_SUCCESS);
|
|
||||||
|
|
||||||
case 'm':
|
|
||||||
macros = *++argv;
|
|
||||||
--argc;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'S':
|
|
||||||
startIocsh = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 's':
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'x':
|
|
||||||
epicsSnprintf(xmacro, sizeof xmacro, "IOC=%s", *++argv);
|
|
||||||
if (dbLoadRecords(exit_db, xmacro)) {
|
|
||||||
epicsExit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
loadedDb = 1;
|
|
||||||
--argc;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
printf("%s: option '%s' not recognized\n", arg0, *argv);
|
|
||||||
usage(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
--argc;
|
|
||||||
++argv;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argc>0 && **argv=='-') {
|
|
||||||
switch((*argv)[1]) {
|
|
||||||
case 'a':
|
|
||||||
case 'd':
|
|
||||||
case 'm':
|
|
||||||
case 'x':
|
|
||||||
printf("%s: missing argument to option '%s'\n", arg0, *argv);
|
|
||||||
usage(EXIT_FAILURE);
|
|
||||||
|
|
||||||
case 'h':
|
|
||||||
usage(EXIT_SUCCESS);
|
|
||||||
|
|
||||||
case 'S':
|
|
||||||
startIocsh = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 's':
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
printf("%s: option '%s' not recognized\n", arg0, *argv);
|
|
||||||
usage(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
--argc;
|
|
||||||
++argv;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loadedDb) {
|
|
||||||
iocInit();
|
|
||||||
epicsThreadSleep(0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* run user's startup script */
|
|
||||||
if (argc>0) {
|
|
||||||
if (iocsh(*argv)) epicsExit(EXIT_FAILURE);
|
|
||||||
epicsThreadSleep(0.2);
|
|
||||||
loadedDb = 1; /* Give it the benefit of the doubt... */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* start an interactive shell if it was requested */
|
|
||||||
if (startIocsh) {
|
|
||||||
iocsh(NULL);
|
|
||||||
} else {
|
|
||||||
if (loadedDb) {
|
|
||||||
epicsThreadExitMain();
|
|
||||||
} else {
|
|
||||||
printf("%s: Nothing to do!\n", arg0);
|
|
||||||
usage(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
epicsExit(EXIT_SUCCESS);
|
|
||||||
/*Note that the following statement will never be executed*/
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,6 @@
|
|||||||
|
|
||||||
#include <pv/sharedPtr.h>
|
#include <pv/sharedPtr.h>
|
||||||
|
|
||||||
#include <shareLib.h>
|
|
||||||
|
|
||||||
struct WorkQueue : private epicsThreadRunable
|
struct WorkQueue : private epicsThreadRunable
|
||||||
{
|
{
|
||||||
typedef std::tr1::weak_ptr<epicsThreadRunable> value_type;
|
typedef std::tr1::weak_ptr<epicsThreadRunable> value_type;
|
||||||
|
|||||||
+3
-2
@@ -44,7 +44,7 @@ testpdb_SRCS += p2pTestIoc_registerRecordDeviceDriver.cpp
|
|||||||
testpdb_LIBS += qsrv
|
testpdb_LIBS += qsrv
|
||||||
TESTS += testpdb
|
TESTS += testpdb
|
||||||
|
|
||||||
PROD_HOST += check_consist
|
TESTPROD_HOST += check_consist
|
||||||
check_consist_SRCS += check_consist.cpp
|
check_consist_SRCS += check_consist.cpp
|
||||||
|
|
||||||
ifdef BASE_3_16
|
ifdef BASE_3_16
|
||||||
@@ -52,7 +52,8 @@ TESTPROD_HOST += testpvalink
|
|||||||
testpvalink_SRCS += testpvalink.cpp
|
testpvalink_SRCS += testpvalink.cpp
|
||||||
testpvalink_SRCS += pvaLinkTestIoc_registerRecordDeviceDriver.cpp
|
testpvalink_SRCS += pvaLinkTestIoc_registerRecordDeviceDriver.cpp
|
||||||
testpvalink_LIBS += qsrv
|
testpvalink_LIBS += qsrv
|
||||||
TESTS += testpvalink
|
# too many false positive failure
|
||||||
|
#TESTS += testpvalink
|
||||||
|
|
||||||
TESTPROD_HOST += testgroupconfig
|
TESTPROD_HOST += testgroupconfig
|
||||||
testgroupconfig_SRCS += testgroupconfig
|
testgroupconfig_SRCS += testgroupconfig
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
record (waveform, "TEST") {
|
||||||
|
field(FTVL, "SHORT")
|
||||||
|
field(NELM, "10")
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ void test_parse()
|
|||||||
" \"+atomic\":false,\n"
|
" \"+atomic\":false,\n"
|
||||||
" \"fld\":{\n"
|
" \"fld\":{\n"
|
||||||
" \"+type\": \"simple\","
|
" \"+type\": \"simple\","
|
||||||
|
" \"+channel\": \"VAL\","
|
||||||
" \"+putorder\": -4"
|
" \"+putorder\": -4"
|
||||||
" },\n"
|
" },\n"
|
||||||
" \"\":{\n"
|
" \"\":{\n"
|
||||||
@@ -29,7 +30,7 @@ void test_parse()
|
|||||||
"}";
|
"}";
|
||||||
|
|
||||||
GroupConfig conf;
|
GroupConfig conf;
|
||||||
GroupConfig::parse(txt, conf);
|
GroupConfig::parse(txt, "rec", conf);
|
||||||
|
|
||||||
testOk(conf.warning.empty(), "Warnings: %s", conf.warning.c_str());
|
testOk(conf.warning.empty(), "Warnings: %s", conf.warning.c_str());
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ void test_parse()
|
|||||||
testOk1(conf.groups["grpa"].atomic_set);
|
testOk1(conf.groups["grpa"].atomic_set);
|
||||||
|
|
||||||
testEqual(conf.groups["grpa"].fields["fld"].type, "simple");
|
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["fld"].putorder, -4);
|
||||||
|
|
||||||
testEqual(conf.groups["grpa"].fields[""].type, "top");
|
testEqual(conf.groups["grpa"].fields[""].type, "top");
|
||||||
@@ -49,15 +50,15 @@ void test_fail()
|
|||||||
|
|
||||||
{
|
{
|
||||||
GroupConfig conf;
|
GroupConfig conf;
|
||||||
testThrows(std::runtime_error, GroupConfig::parse("{", conf));
|
testThrows(std::runtime_error, GroupConfig::parse("{", "", conf));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
GroupConfig 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;
|
GroupConfig conf;
|
||||||
testThrows(std::runtime_error, GroupConfig::parse("{\"G\":5}", conf));
|
testThrows(std::runtime_error, GroupConfig::parse("{\"G\":5}", "", conf));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+71
-3
@@ -144,6 +144,12 @@ void testSingleMonitor(pvac::ClientProvider& client)
|
|||||||
if(!mon.poll())
|
if(!mon.poll())
|
||||||
testAbort("Data event w/o data");
|
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()
|
testEqual(mon.changed, pvd::BitSet()
|
||||||
.set(mon.root->getSubFieldT("value")->getFieldOffset())
|
.set(mon.root->getSubFieldT("value")->getFieldOffset())
|
||||||
.set(mon.root->getSubFieldT("alarm.severity")->getFieldOffset())
|
.set(mon.root->getSubFieldT("alarm.severity")->getFieldOffset())
|
||||||
@@ -214,6 +220,12 @@ void testGroupMonitor(pvac::ClientProvider& client)
|
|||||||
if(!mon.poll())
|
if(!mon.poll())
|
||||||
testAbort("Data event w/o data");
|
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()
|
testEqual(mon.changed, pvd::BitSet()
|
||||||
.set(mon.root->getSubFieldT("fld1.value")->getFieldOffset())
|
.set(mon.root->getSubFieldT("fld1.value")->getFieldOffset())
|
||||||
.set(mon.root->getSubFieldT("fld1.alarm.severity")->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);
|
testFieldEqual<pvd::PVDouble>(mon.root, "fld1.value", 32.0);
|
||||||
#else
|
#else
|
||||||
testSkip(20, "No multilock");
|
testSkip(21, "No multilock");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,6 +278,14 @@ void testGroupMonitorTriggers(pvac::ClientProvider& client)
|
|||||||
|
|
||||||
testShow()<<mon.root;
|
testShow()<<mon.root;
|
||||||
#define OFF(NAME) mon.root->getSubFieldT(NAME)->getFieldOffset()
|
#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()
|
testEqual(mon.changed, pvd::BitSet()
|
||||||
.set(OFF("fld1.value"))
|
.set(OFF("fld1.value"))
|
||||||
.set(OFF("fld1.alarm.severity"))
|
.set(OFF("fld1.alarm.severity"))
|
||||||
@@ -288,10 +308,49 @@ void testGroupMonitorTriggers(pvac::ClientProvider& client)
|
|||||||
|
|
||||||
testOk1(!mon.poll());
|
testOk1(!mon.poll());
|
||||||
#else
|
#else
|
||||||
testSkip(19, "No multilock");
|
testSkip(21, "No multilock");
|
||||||
#endif
|
#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
|
} // namespace
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
@@ -299,7 +358,7 @@ void p2pTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
|||||||
|
|
||||||
MAIN(testpdb)
|
MAIN(testpdb)
|
||||||
{
|
{
|
||||||
testPlan(93);
|
testPlan(99);
|
||||||
try{
|
try{
|
||||||
QSRVRegistrar_counters();
|
QSRVRegistrar_counters();
|
||||||
epics::RefSnapshot ref_before;
|
epics::RefSnapshot ref_before;
|
||||||
@@ -318,6 +377,7 @@ MAIN(testpdb)
|
|||||||
#ifdef USE_MULTILOCK
|
#ifdef USE_MULTILOCK
|
||||||
testdbReadDatabase("testpdb-groups.db", NULL, NULL);
|
testdbReadDatabase("testpdb-groups.db", NULL, NULL);
|
||||||
#endif
|
#endif
|
||||||
|
testdbReadDatabase("testfilters.db", NULL, NULL);
|
||||||
|
|
||||||
IOC.init();
|
IOC.init();
|
||||||
|
|
||||||
@@ -333,10 +393,16 @@ MAIN(testpdb)
|
|||||||
testSingleMonitor(client);
|
testSingleMonitor(client);
|
||||||
testGroupMonitor(client);
|
testGroupMonitor(client);
|
||||||
testGroupMonitorTriggers(client);
|
testGroupMonitorTriggers(client);
|
||||||
|
testFilters(client);
|
||||||
|
|
||||||
testEqual(epics::atomic::get(PDBProvider::num_instances), 1u);
|
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());
|
testOk1(prov.unique());
|
||||||
prov.reset();
|
prov.reset();
|
||||||
|
|
||||||
@@ -358,6 +424,8 @@ MAIN(testpdb)
|
|||||||
#endif // USE_MULTILOCK
|
#endif // USE_MULTILOCK
|
||||||
testEqual(epics::atomic::get(PDBSinglePV::num_instances), 0u);
|
testEqual(epics::atomic::get(PDBSinglePV::num_instances), 0u);
|
||||||
|
|
||||||
|
testTodoEnd();
|
||||||
|
|
||||||
}catch(std::exception& e){
|
}catch(std::exception& e){
|
||||||
PRINT_EXCEPTION(e);
|
PRINT_EXCEPTION(e);
|
||||||
testAbort("Unexpected Exception: %s", e.what());
|
testAbort("Unexpected Exception: %s", e.what());
|
||||||
|
|||||||
+27
-1
@@ -61,6 +61,31 @@ void testPut()
|
|||||||
testdbGetFieldEqual("src:o2.VAL", DBF_INT64, 14LL);
|
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
|
} // namespace
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
@@ -68,7 +93,7 @@ void pvaLinkTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
|||||||
|
|
||||||
MAIN(testpvalink)
|
MAIN(testpvalink)
|
||||||
{
|
{
|
||||||
testPlan(15);
|
testPlan(20);
|
||||||
|
|
||||||
// Disable PVA client provider, use local/QSRV provider
|
// Disable PVA client provider, use local/QSRV provider
|
||||||
pvaLinkIsolate = 1;
|
pvaLinkIsolate = 1;
|
||||||
@@ -83,6 +108,7 @@ MAIN(testpvalink)
|
|||||||
IOC.init();
|
IOC.init();
|
||||||
testGet();
|
testGet();
|
||||||
testPut();
|
testPut();
|
||||||
|
testPutAsync();
|
||||||
testqsrvShutdownOk();
|
testqsrvShutdownOk();
|
||||||
IOC.shutdown();
|
IOC.shutdown();
|
||||||
testqsrvCleanup();
|
testqsrvCleanup();
|
||||||
|
|||||||
+36
-2
@@ -8,7 +8,7 @@ record(ai, "target:ai") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
record(int64in, "src:i1") {
|
record(int64in, "src:i1") {
|
||||||
field(INP, {pva:"target:i"})
|
field(INP, {"pva":"target:i"})
|
||||||
}
|
}
|
||||||
|
|
||||||
# used by testPut()
|
# used by testPut()
|
||||||
@@ -17,5 +17,39 @@ record(int64in, "target:i2") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
record(int64out, "src:o2") {
|
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
@@ -72,40 +72,60 @@ void testScalar()
|
|||||||
testEqual(dbChannelFinalFieldType(chan_mbbi), DBR_ENUM);
|
testEqual(dbChannelFinalFieldType(chan_mbbi), DBR_ENUM);
|
||||||
#ifdef USE_INT64
|
#ifdef USE_INT64
|
||||||
testEqual(dbChannelFinalFieldType(chan_i64), DBR_INT64);
|
testEqual(dbChannelFinalFieldType(chan_i64), DBR_INT64);
|
||||||
|
#else
|
||||||
|
testSkip(1, "!USE_INT64");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ScalarBuilder builder;
|
pvd::PVStructurePtr root;
|
||||||
|
p2p::auto_ptr<PVIF> pvif_li;
|
||||||
pvd::FieldConstPtr dtype_li(builder.dtype(chan_li));
|
|
||||||
#ifdef USE_INT64
|
#ifdef USE_INT64
|
||||||
pvd::FieldConstPtr dtype_i64(builder.dtype(chan_i64));
|
p2p::auto_ptr<PVIF> pvif_i64;
|
||||||
#endif
|
#endif
|
||||||
pvd::FieldConstPtr dtype_si(builder.dtype(chan_si));
|
p2p::auto_ptr<PVIF> pvif_si;
|
||||||
pvd::FieldConstPtr dtype_ai(builder.dtype(chan_ai));
|
p2p::auto_ptr<PVIF> pvif_ai;
|
||||||
pvd::FieldConstPtr dtype_ai_rval(builder.dtype(chan_ai_rval));
|
p2p::auto_ptr<PVIF> pvif_ai_rval;
|
||||||
pvd::FieldConstPtr dtype_mbbi(builder.dtype(chan_mbbi));
|
p2p::auto_ptr<PVIF> pvif_mbbi;
|
||||||
|
{
|
||||||
pvd::StructureConstPtr dtype_root(pvd::getFieldCreate()->createFieldBuilder()
|
ScalarBuilder builder_li(chan_li);
|
||||||
->add("li", dtype_li)
|
|
||||||
#ifdef USE_INT64
|
#ifdef USE_INT64
|
||||||
->add("i64", dtype_i64)
|
ScalarBuilder builder_i64(chan_i64);
|
||||||
#endif
|
#endif
|
||||||
->add("si", dtype_si)
|
ScalarBuilder builder_si(chan_si);
|
||||||
->add("ai", dtype_ai)
|
ScalarBuilder builder_ai(chan_ai);
|
||||||
->add("ai_rval", dtype_ai_rval)
|
ScalarBuilder builder_ai_rval(chan_ai_rval);
|
||||||
->add("mbbi", dtype_mbbi)
|
ScalarBuilder builder_mbbi(chan_mbbi);
|
||||||
->createStructure());
|
|
||||||
|
|
||||||
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")));
|
pvd::StructureConstPtr dtype_root(pvd::getFieldCreate()->createFieldBuilder()
|
||||||
#ifdef USE_INT64
|
->add("li", dtype_li)
|
||||||
p2p::auto_ptr<PVIF> pvif_i64(builder.attach(chan_i64, root, FieldName("i64")));
|
#ifdef USE_INT64
|
||||||
#endif
|
->add("i64", dtype_i64)
|
||||||
p2p::auto_ptr<PVIF> pvif_si(builder.attach(chan_si, root, FieldName("si")));
|
#endif
|
||||||
p2p::auto_ptr<PVIF> pvif_ai(builder.attach(chan_ai, root, FieldName("ai")));
|
->add("si", dtype_si)
|
||||||
p2p::auto_ptr<PVIF> pvif_ai_rval(builder.attach(chan_ai_rval, root, FieldName("ai_rval")));
|
->add("ai", dtype_ai)
|
||||||
p2p::auto_ptr<PVIF> pvif_mbbi(builder.attach(chan_mbbi, root, FieldName("mbbi")));
|
->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;
|
testShow()<<"Entire structure\n"<<root;
|
||||||
|
|
||||||
@@ -118,6 +138,12 @@ void testScalar()
|
|||||||
dbScanUnlock((dbCommon*)prec_li);
|
dbScanUnlock((dbCommon*)prec_li);
|
||||||
|
|
||||||
#define OFF(NAME) (epicsUInt32)root->getSubFieldT(NAME)->getFieldOffset()
|
#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()
|
testEqual(mask, pvd::BitSet()
|
||||||
.set(OFF("li.value"))
|
.set(OFF("li.value"))
|
||||||
.set(OFF("li.alarm.severity"))
|
.set(OFF("li.alarm.severity"))
|
||||||
@@ -150,6 +176,12 @@ void testScalar()
|
|||||||
dbScanUnlock((dbCommon*)prec_i64);
|
dbScanUnlock((dbCommon*)prec_i64);
|
||||||
|
|
||||||
#define OFF(NAME) (epicsUInt32)root->getSubFieldT(NAME)->getFieldOffset()
|
#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()
|
testEqual(mask, pvd::BitSet()
|
||||||
.set(OFF("i64.value"))
|
.set(OFF("i64.value"))
|
||||||
.set(OFF("i64.alarm.severity"))
|
.set(OFF("i64.alarm.severity"))
|
||||||
@@ -173,7 +205,10 @@ void testScalar()
|
|||||||
#undef OFF
|
#undef OFF
|
||||||
mask.clear();
|
mask.clear();
|
||||||
|
|
||||||
#endif
|
#else // !USE_INT64
|
||||||
|
testSkip(2, "!USE_INT64");
|
||||||
|
|
||||||
|
#endif // USE_INT64
|
||||||
|
|
||||||
dbScanLock((dbCommon*)prec_si);
|
dbScanLock((dbCommon*)prec_si);
|
||||||
prec_si->time.secPastEpoch = 0x12345678;
|
prec_si->time.secPastEpoch = 0x12345678;
|
||||||
@@ -182,6 +217,12 @@ void testScalar()
|
|||||||
dbScanUnlock((dbCommon*)prec_si);
|
dbScanUnlock((dbCommon*)prec_si);
|
||||||
|
|
||||||
#define OFF(NAME) (epicsUInt32)root->getSubFieldT(NAME)->getFieldOffset()
|
#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()
|
testEqual(mask, pvd::BitSet()
|
||||||
.set(OFF("si.value"))
|
.set(OFF("si.value"))
|
||||||
.set(OFF("si.alarm.severity"))
|
.set(OFF("si.alarm.severity"))
|
||||||
@@ -189,7 +230,6 @@ void testScalar()
|
|||||||
.set(OFF("si.alarm.message"))
|
.set(OFF("si.alarm.message"))
|
||||||
.set(OFF("si.timeStamp.secondsPastEpoch"))
|
.set(OFF("si.timeStamp.secondsPastEpoch"))
|
||||||
.set(OFF("si.timeStamp.nanoseconds"))
|
.set(OFF("si.timeStamp.nanoseconds"))
|
||||||
//.set(OFF("si.timeStamp.userTag"))
|
|
||||||
.set(OFF("si.display.limitHigh"))
|
.set(OFF("si.display.limitHigh"))
|
||||||
.set(OFF("si.display.limitLow"))
|
.set(OFF("si.display.limitLow"))
|
||||||
.set(OFF("si.display.description"))
|
.set(OFF("si.display.description"))
|
||||||
@@ -210,6 +250,14 @@ void testScalar()
|
|||||||
dbScanUnlock((dbCommon*)prec_ai);
|
dbScanUnlock((dbCommon*)prec_ai);
|
||||||
|
|
||||||
#define OFF(NAME) (epicsUInt32)root->getSubFieldT(NAME)->getFieldOffset()
|
#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()
|
testEqual(mask, pvd::BitSet()
|
||||||
.set(OFF("ai.value"))
|
.set(OFF("ai.value"))
|
||||||
.set(OFF("ai.alarm.severity"))
|
.set(OFF("ai.alarm.severity"))
|
||||||
@@ -217,7 +265,6 @@ void testScalar()
|
|||||||
.set(OFF("ai.alarm.message"))
|
.set(OFF("ai.alarm.message"))
|
||||||
.set(OFF("ai.timeStamp.secondsPastEpoch"))
|
.set(OFF("ai.timeStamp.secondsPastEpoch"))
|
||||||
.set(OFF("ai.timeStamp.nanoseconds"))
|
.set(OFF("ai.timeStamp.nanoseconds"))
|
||||||
//.set(OFF("ai.timeStamp.userTag"))
|
|
||||||
.set(OFF("ai.display.limitHigh"))
|
.set(OFF("ai.display.limitHigh"))
|
||||||
.set(OFF("ai.display.limitLow"))
|
.set(OFF("ai.display.limitLow"))
|
||||||
.set(OFF("ai.display.description"))
|
.set(OFF("ai.display.description"))
|
||||||
@@ -236,7 +283,6 @@ void testScalar()
|
|||||||
.set(OFF("ai_rval.alarm.message"))
|
.set(OFF("ai_rval.alarm.message"))
|
||||||
.set(OFF("ai_rval.timeStamp.secondsPastEpoch"))
|
.set(OFF("ai_rval.timeStamp.secondsPastEpoch"))
|
||||||
.set(OFF("ai_rval.timeStamp.nanoseconds"))
|
.set(OFF("ai_rval.timeStamp.nanoseconds"))
|
||||||
//.set(OFF("ai_rval.timeStamp.userTag"))
|
|
||||||
.set(OFF("ai_rval.display.limitHigh"))
|
.set(OFF("ai_rval.display.limitHigh"))
|
||||||
.set(OFF("ai_rval.display.limitLow"))
|
.set(OFF("ai_rval.display.limitLow"))
|
||||||
.set(OFF("ai_rval.display.description"))
|
.set(OFF("ai_rval.display.description"))
|
||||||
@@ -297,6 +343,8 @@ void testScalar()
|
|||||||
testFieldEqual<pvd::PVString>(root, "i64.display.units", "arb");
|
testFieldEqual<pvd::PVString>(root, "i64.display.units", "arb");
|
||||||
testTodoEnd();
|
testTodoEnd();
|
||||||
testFieldEqual<pvd::PVInt>(root, "i64.display.precision", 0);
|
testFieldEqual<pvd::PVInt>(root, "i64.display.precision", 0);
|
||||||
|
#else
|
||||||
|
testSkip(9, "!USE_INT64");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
testFieldEqual<pvd::PVString>(root, "si.value", "hello");
|
testFieldEqual<pvd::PVString>(root, "si.value", "hello");
|
||||||
@@ -362,15 +410,15 @@ void testScalar()
|
|||||||
pvif_i64->get(mask);
|
pvif_i64->get(mask);
|
||||||
testEqual(prec_i64->val, epicsInt64(-0x8000000000000000LL));
|
testEqual(prec_i64->val, epicsInt64(-0x8000000000000000LL));
|
||||||
dbScanUnlock((dbCommon*)prec_i64);
|
dbScanUnlock((dbCommon*)prec_i64);
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_INT64
|
|
||||||
dbScanLock((dbCommon*)prec_i64);
|
dbScanLock((dbCommon*)prec_i64);
|
||||||
mask.clear();
|
mask.clear();
|
||||||
mask.set(root->getSubFieldT("i64.value")->getFieldOffset());
|
mask.set(root->getSubFieldT("i64.value")->getFieldOffset());
|
||||||
pvif_i64->get(mask);
|
pvif_i64->get(mask);
|
||||||
testEqual(prec_i64->val, epicsInt64(-0x8000000000000000LL));
|
testEqual(prec_i64->val, epicsInt64(-0x8000000000000000LL));
|
||||||
dbScanUnlock((dbCommon*)prec_i64);
|
dbScanUnlock((dbCommon*)prec_i64);
|
||||||
|
#else
|
||||||
|
testSkip(2, "!USE_INT64");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
dbScanLock((dbCommon*)prec_si);
|
dbScanLock((dbCommon*)prec_si);
|
||||||
@@ -424,30 +472,37 @@ void testPlain()
|
|||||||
DBCH chan_ai("test:ai");
|
DBCH chan_ai("test:ai");
|
||||||
DBCH chan_mbbi("test:mbbi");
|
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;
|
pvd::BitSet mask;
|
||||||
|
|
||||||
mask.clear();
|
mask.clear();
|
||||||
@@ -521,15 +576,73 @@ void testPlain()
|
|||||||
dbScanUnlock((dbCommon*)prec_mbbi);
|
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
|
} // namespace
|
||||||
|
|
||||||
MAIN(testpvif)
|
MAIN(testpvif)
|
||||||
{
|
{
|
||||||
testPlan(75
|
testPlan(98);
|
||||||
#ifdef USE_INT64
|
|
||||||
+13
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
#ifdef USE_INT64
|
#ifdef USE_INT64
|
||||||
testDiag("Testing of 64-bit field access");
|
testDiag("Testing of 64-bit field access");
|
||||||
#else
|
#else
|
||||||
@@ -537,5 +650,6 @@ MAIN(testpvif)
|
|||||||
#endif
|
#endif
|
||||||
testScalar();
|
testScalar();
|
||||||
testPlain();
|
testPlain();
|
||||||
|
testFilters();
|
||||||
return testDone();
|
return testDone();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user