Compare commits
134 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b1d28cca39 | |||
| 50be5b6025 | |||
| 8e7a1ad295 | |||
| e894ae753f | |||
| 4559f483bf | |||
| e843db5d27 | |||
| 8363c870bd | |||
| 9499137bb4 | |||
| c4798dc2bc | |||
| 11eeed9d0c | |||
| 94d1eedc75 | |||
| cb13435d15 | |||
| cda2222ed5 | |||
| 68708ff530 | |||
| 137ecfed56 | |||
| 29a6f261dc | |||
| 1d9fbbea0b | |||
| 536f4dd02f | |||
| 21ae754869 | |||
| 3024f9fb0c | |||
| 58a2159969 | |||
| c200dd22f1 | |||
| 9c6fb5c539 | |||
| 54a882d844 | |||
| c7a54de469 | |||
| c2ee7c9dc4 | |||
| 3e59a4b6a8 | |||
| 0e0400022c | |||
| bdbf57350b | |||
| 2e58e54e1d | |||
| ddfd746c62 | |||
| e41269230c | |||
| c3b6fc08b8 | |||
| d70a2ff8c3 | |||
| ce39c93201 | |||
| d7314eaef4 | |||
| 5170c2230d | |||
| 3ed88f2559 | |||
| 2c533d79ab | |||
| a72bc31a44 | |||
| 521154fd52 | |||
| 6d1cbd87fd | |||
| 68904852b9 | |||
| a3dc712174 | |||
| 1ce449d40d | |||
| 67b384bb6c | |||
| f3d7d7cd8f | |||
| 7bc5cbf957 | |||
| 0ab4f937a7 | |||
| 3c5a5c805c | |||
| c79074e7fa | |||
| a245adb5cf | |||
| c1cec998a9 | |||
| c80a88c112 | |||
| 1d689d7e96 | |||
| 95b23fc6bc | |||
| afead32e9d | |||
| db3fdf54e9 | |||
| 5f434aec59 | |||
| a4f35714a0 | |||
| dfd28f5474 | |||
| 0d69aab6e9 | |||
| b157d73c0b | |||
| b215396225 | |||
| 17a3324cc8 | |||
| da3ecc08b4 | |||
| c126f7a1a2 | |||
| f86d61274a | |||
| 9ac4431d6c | |||
| 365e5061af | |||
| ec05be4e2f | |||
| 76c6473b6f | |||
| 3338d1035d | |||
| b3ffc49f71 | |||
| ff67302347 | |||
| b18777e8fe | |||
| 60699c48c2 | |||
| 99ca9b958e | |||
| 9839116c56 | |||
| 40e407de8c | |||
| 701d96fde1 | |||
| 8db7a4a75e | |||
| a22f6b0801 | |||
| e3c0703df1 | |||
| 75efc13244 | |||
| f3622c0d42 | |||
| 3a5ac39627 | |||
| e70aa0897d | |||
| af761958e5 | |||
| 0a20804294 | |||
| ff197f89cb | |||
| c86472666a | |||
| e2147a95b2 | |||
| d07d2dd30c | |||
| 197e079ad5 | |||
| 27fd0868cf | |||
| e165919e84 | |||
| 2934c8a1e4 | |||
| c0ac33dc4b | |||
| 4357d9079e | |||
| afe5232653 | |||
| 1cc1d1857e | |||
| 6089591abb | |||
| c55d50c158 | |||
| 520ac5a0b5 | |||
| 9192a872c7 | |||
| 79bd35dd3c | |||
| f0be2f7016 | |||
| d5ee917c6d | |||
| cd2c1762b7 | |||
| 8b8e421a65 | |||
| f3173682fb | |||
| 266f2f9d2d | |||
| 19602bb06d | |||
| 26d369c042 | |||
| 1d0be31d3f | |||
| 4a0275a476 | |||
| 9c10c82e82 | |||
| 770152e621 | |||
| 272b4fb9cb | |||
| a22399fdf4 | |||
| d229efbb66 | |||
| 392a44b791 | |||
| 6982fa2d8f | |||
| b7de8ba8b0 | |||
| a1cb3d302a | |||
| b6b6ac3305 | |||
| 1635e75c86 | |||
| 6e77fc85b6 | |||
| 8a9a3550b9 | |||
| 95a74aa134 | |||
| 9cc417103b | |||
| 399f6b311e | |||
| 3be469e5c2 |
+102
@@ -0,0 +1,102 @@
|
||||
# .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'
|
||||
|
||||
# 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: mingw
|
||||
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/appveyor/do.py prepare
|
||||
- cmd: python .ci/appveyor/do.py build
|
||||
|
||||
test_script:
|
||||
- cmd: python .ci/appveyor/do.py test
|
||||
|
||||
on_finish:
|
||||
- ps: Get-ChildItem *.tap -Recurse -Force | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
|
||||
- cmd: python .ci/appveyor/do.py build test-results -s
|
||||
|
||||
#---------------------------------#
|
||||
# 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:
|
||||
- me@example.com
|
||||
on_build_success: false
|
||||
|
||||
- provider: GitHubPullRequest
|
||||
Submodule
+1
Submodule .ci added at ecb7e43660
@@ -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,20 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e -x
|
||||
|
||||
# set RTEMS to eg. "4.9" or "4.10"
|
||||
# requires qemu, bison, flex, texinfo, install-info
|
||||
if [ -n "$RTEMS" ]
|
||||
then
|
||||
# find local qemu-system-i386
|
||||
echo -n "Using QEMU: "
|
||||
type qemu-system-i386 || echo "Missing qemu"
|
||||
EXTRA=RTEMS_QEMU_FIXUPS=YES
|
||||
fi
|
||||
|
||||
make -j2 $EXTRA
|
||||
|
||||
if [ "$TEST" != "NO" ]
|
||||
then
|
||||
make tapfiles
|
||||
make -s test-results
|
||||
fi
|
||||
@@ -1,153 +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_base_module() {
|
||||
MODULE=$1
|
||||
BRANCH=$2
|
||||
( cd epics-base/modules && \
|
||||
git clone --quiet --depth 5 --branch $MODULE/$BRANCH https://github.com/${REPOBASE:-epics-base}/epics-base.git $MODULE && \
|
||||
cd $MODULE && git log -n1 )
|
||||
}
|
||||
|
||||
add_gh_module() {
|
||||
MODULE=$1
|
||||
REPOOWNER=$2
|
||||
REPONAME=$3
|
||||
BRANCH=$4
|
||||
( cd epics-base/modules && \
|
||||
git clone --quiet --depth 5 --branch $BRANCH https://github.com/$REPOOWNER/$REPONAME.git $MODULE && \
|
||||
cd $MODULE && git log -n1 )
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if [ "$BRBASE" ]
|
||||
then
|
||||
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}
|
||||
else
|
||||
git clone --quiet --depth 5 --branch core/"${BRCORE:-master}" https://github.com/${REPOBASE:-epics-base}/epics-base.git epics-base
|
||||
( cd epics-base && git log -n1 )
|
||||
add_base_module libcom "${BRLIBCOM:-master}"
|
||||
add_base_module ca "${BRCA:-master}"
|
||||
add_base_module database "${BRDATABASE:-master}"
|
||||
add_gh_module pvData ${REPOPVD:-epics-base} pvDataCPP ${BRPVD:-master}
|
||||
add_gh_module pvAccess ${REPOPVA:-epics-base} pvAccessCPP ${BRPVA:-master}
|
||||
fi
|
||||
|
||||
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"
|
||||
install -d /home/travis/.cache
|
||||
curl -L "https://github.com/mdavidsaver/rsb/releases/download/travis-20160306-2/rtems${RTEMS}-i386-trusty-20190306-2.tar.gz" \
|
||||
| tar -C /home/travis/.cache -xj
|
||||
|
||||
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/travis/.cache/rtems${RTEMS}-i386
|
||||
EOF
|
||||
cat << EOF >> epics-base/configure/CONFIG_SITE
|
||||
CROSS_COMPILER_TARGET_ARCHS+=RTEMS-pc386
|
||||
EOF
|
||||
|
||||
# find local qemu-system-i386
|
||||
export PATH="$HOME/.cache/qemu/usr/bin:$PATH"
|
||||
echo -n "Using QEMU: "
|
||||
type qemu-system-i386 || echo "Missing qemu"
|
||||
cat <<EOF >> epics-base/configure/CONFIG_SITE
|
||||
RTEMS_QEMU_FIXUPS=YES
|
||||
EOF
|
||||
fi
|
||||
|
||||
make -j2 -C epics-base $EXTRA
|
||||
|
||||
if [ "$BRBASE" ]
|
||||
then
|
||||
make -j2 -C pvData $EXTRA
|
||||
make -j2 -C pvAccess $EXTRA
|
||||
fi
|
||||
|
||||
find epics-base/include
|
||||
find epics-base/lib
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule ".ci"]
|
||||
path = .ci
|
||||
url = https://github.com/epics-base/ci-scripts
|
||||
@@ -0,0 +1,17 @@
|
||||
# .readthedocs.yml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Build documentation in the documentation/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: documentation/conf.py
|
||||
|
||||
# Build documentation with MkDocs
|
||||
#mkdocs:
|
||||
# configuration: mkdocs.yml
|
||||
|
||||
# Optionally build your docs in additional formats such as PDF and ePub
|
||||
formats: all
|
||||
+98
-19
@@ -1,29 +1,108 @@
|
||||
sudo: false
|
||||
dist: trusty
|
||||
language: c++
|
||||
compiler:
|
||||
- gcc
|
||||
# .travis.yml for use with EPICS Base ci-scripts
|
||||
# (see: https://github.com/epics-base/ci-scripts)
|
||||
|
||||
# This is YAML - indentation levels are crucial
|
||||
|
||||
language: cpp
|
||||
compiler: gcc
|
||||
dist: bionic
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache
|
||||
|
||||
env:
|
||||
global:
|
||||
- SETUP_PATH=.ci-local:.ci
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
# for all EPICS builds
|
||||
- libreadline6-dev
|
||||
- libncurses5-dev
|
||||
- perl
|
||||
# for clang compiler
|
||||
- clang
|
||||
# for mingw builds (32bit and 64bit)
|
||||
- g++-mingw-w64-i686
|
||||
- g++-mingw-w64-x86-64
|
||||
# for RTEMS cross builds
|
||||
- qemu-system-x86
|
||||
|
||||
install:
|
||||
- ./.ci/travis-prepare.sh
|
||||
- ./.ci/travis/prepare.sh
|
||||
|
||||
script:
|
||||
- ./.ci/travis-build.sh
|
||||
env:
|
||||
- BRCORE=master BRLIBCOM=master BRPVD=master BRPVA=master
|
||||
- CMPLR=clang
|
||||
- EXTRA="CMD_CXXFLAGS=-std=c++98"
|
||||
- EXTRA="CMD_CXXFLAGS=-std=c++11"
|
||||
- CMPLR=clang EXTRA="CMD_CXXFLAGS=-std=c++11"
|
||||
- WINE=32 TEST=NO STATIC=YES
|
||||
- WINE=32 TEST=NO STATIC=NO
|
||||
- RTEMS=4.10 TEST=NO
|
||||
- RTEMS=4.9 TEST=NO
|
||||
- BRBASE=3.16
|
||||
- BRBASE=3.15
|
||||
- ./.ci/travis/build.sh
|
||||
|
||||
# If you need to do more during install and build,
|
||||
# add a local directory to your module and do e.g.
|
||||
# - ./.ci-local/travis/install-extras.sh
|
||||
|
||||
# Define build jobs
|
||||
|
||||
# Well-known variables to use
|
||||
# SET source setup file
|
||||
# EXTRA content will be added to make command line
|
||||
# STATIC set to YES for static build (default: NO)
|
||||
# TEST set to NO to skip running the tests (default: YES)
|
||||
# VV set to make build scripts verbose (default: unset)
|
||||
|
||||
# Usually from setup files, but may be specified or overridden
|
||||
# on a job line
|
||||
# MODULES list of dependency modules
|
||||
# BASE branch or release tag name of the EPICS Base to use
|
||||
# <MODULE> branch or release tag for a specific module
|
||||
# ... see README for setup file syntax description
|
||||
|
||||
jobs:
|
||||
include:
|
||||
|
||||
# Different configurations of default gcc and clang
|
||||
|
||||
- env: BASE=7.0
|
||||
|
||||
- env: BASE=7.0
|
||||
compiler: clang
|
||||
|
||||
- env: BASE=7.0 EXTRA="CMD_CXXFLAGS=-std=c++11"
|
||||
|
||||
- env: BASE=7.0 EXTRA="CMD_CXXFLAGS=-std=c++11"
|
||||
compiler: clang
|
||||
|
||||
# Trusty: compiler versions very close to RHEL 7
|
||||
|
||||
- env: BASE=7.0
|
||||
dist: trusty
|
||||
|
||||
- env: BASE=7.0 EXTRA="CMD_CXXFLAGS=-std=c++11"
|
||||
dist: trusty
|
||||
|
||||
- env: BASE=3.15 EXTRA="CMD_CXXFLAGS=-std=c++11"
|
||||
dist: trusty
|
||||
|
||||
# Cross-compilations to Windows using MinGW and WINE
|
||||
|
||||
- env: BASE=7.0 WINE=32 TEST=NO STATIC=YES
|
||||
compiler: mingw
|
||||
|
||||
- env: BASE=7.0 WINE=64 TEST=NO STATIC=NO
|
||||
compiler: mingw
|
||||
|
||||
# Other gcc versions (added as an extra package)
|
||||
|
||||
- env: BASE=7.0
|
||||
compiler: gcc-6
|
||||
addons: { apt: { packages: ["g++-6"], sources: ["ubuntu-toolchain-r-test"] } }
|
||||
|
||||
- env: BASE=7.0
|
||||
compiler: gcc-7
|
||||
addons: { apt: { packages: ["g++-7"], sources: ["ubuntu-toolchain-r-test"] } }
|
||||
|
||||
# MacOS build
|
||||
|
||||
- env: BASE=7.0
|
||||
os: osx
|
||||
compiler: clang
|
||||
addons: { homebrew: { packages: ["re2c"], update: true } }
|
||||
|
||||
@@ -7,6 +7,9 @@ DIRS += configure
|
||||
DIRS += $(wildcard *App)
|
||||
DIRS += $(wildcard iocBoot)
|
||||
|
||||
# pdbApp depends on configure for CONFIG_QSRV_VERSION
|
||||
pdbApp_DEPEND_DIRS = configure
|
||||
|
||||
# iocBoot depends on all *App dirs
|
||||
iocBoot_DEPEND_DIRS += $(filter %App,$(DIRS))
|
||||
testApp_DEPEND_DIRS += p2pApp pdbApp
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
See main [documentation](https://epics-base.github.io/pva2pva/) page.
|
||||
|
||||
This repository contains two distinct pieces of software.
|
||||
|
||||
QSRV
|
||||
@@ -23,24 +25,22 @@ Dependencies
|
||||
------------
|
||||
|
||||
- [epics-base](http://www.aps.anl.gov/epics/) >= 3.15.3
|
||||
- and
|
||||
- [pvDataCPP](http://epics-pvdata.sourceforge.net/)
|
||||
- [pvAccessCPP](http://epics-pvdata.sourceforge.net/)
|
||||
|
||||
or bundled with
|
||||
|
||||
- [epics-base](http://www.aps.anl.gov/epics/) >= 7.0.1
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
To build all dependencies from source:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/epics-base/epics-base.git
|
||||
git clone https://github.com/epics-base/pvDataCPP.git
|
||||
git clone https://github.com/epics-base/pvAccessCPP.git
|
||||
git clone https://github.com/mdavidsaver/pva2pva.git
|
||||
|
||||
git clone --recursive https://github.com/epics-base/epics-base.git
|
||||
make -C epics-base
|
||||
make -C pvDataCPP
|
||||
make -C pvAccessCPP
|
||||
make -C pva2pva
|
||||
```
|
||||
|
||||
Running QSRV
|
||||
@@ -86,15 +86,15 @@ A full list of `info(Q:group` options.
|
||||
record(...) {
|
||||
info(Q:group, {
|
||||
"<group_name>":{
|
||||
+id:"some/NT:1.0", // top level ID
|
||||
+meta:"FLD", // map top level alarm/timeStamp
|
||||
+atomic:true, // whether monitors default to multi-locking atomicity
|
||||
+id:"some/NT:1.0", # top level ID
|
||||
+meta:"FLD", # map top level alarm/timeStamp
|
||||
+atomic:true, # whether monitors default to multi-locking atomicity
|
||||
"<field.name>":{
|
||||
+type:"scalar", // controls how map VAL mapped onto <field.name>
|
||||
+type:"scalar", # controls how map VAL mapped onto <field.name>
|
||||
+channel:"VAL",
|
||||
+id:"some/NT:1.0",
|
||||
+trigger:"*", // "*" or comma seperated list of <field.name>s
|
||||
+putorder:0, // set for fields where put is allowed, processing done in increasing order
|
||||
+trigger:"*", # "*" or comma seperated list of <field.name>s
|
||||
+putorder:0, # set for fields where put is allowed, processing done in increasing order
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -109,9 +109,7 @@ At present each pva2pva process can act as a uni-directional proxy,
|
||||
presenting a pvAccess server on one interface,
|
||||
and a client on other(s).
|
||||
|
||||
The file [example.cmd](example.cmd) provides a starting point.
|
||||
Adjust *EPICS_PVAS_INTF_ADDR_LIST* and *EPICS_PVA_ADDR_LIST*
|
||||
according to the host computer's network configuration.
|
||||
The file [loopback.conf](loopback.conf) provides a starting point.
|
||||
|
||||
At present there are no safe guard against creating loops
|
||||
where a gateway client side connects to its own server side.
|
||||
@@ -121,5 +119,5 @@ the interface used for the server (either directly, or included in a broadcast d
|
||||
|
||||
```
|
||||
cd pva2pva
|
||||
./bin/linux-x86_64/pva2pva example.cmd
|
||||
./bin/linux-x86_64/pva2pva loopback.conf
|
||||
```
|
||||
|
||||
+86
-68
@@ -74,8 +74,9 @@ struct BaseMonitor : public epics::pvAccess::Monitor
|
||||
|
||||
typedef epics::pvAccess::MonitorRequester requester_t;
|
||||
|
||||
mutable epicsMutex lock; // not held during any callback
|
||||
epicsMutex& lock; // not held during any callback
|
||||
typedef epicsGuard<epicsMutex> guard_t;
|
||||
typedef epicsGuardRelease<epicsMutex> unguard_t;
|
||||
|
||||
private:
|
||||
const requester_t::weak_pointer requester;
|
||||
@@ -90,9 +91,11 @@ private:
|
||||
buffer_t inuse, empty;
|
||||
|
||||
public:
|
||||
BaseMonitor(const requester_t::weak_pointer& requester,
|
||||
BaseMonitor(epicsMutex& lock,
|
||||
const requester_t::weak_pointer& requester,
|
||||
const epics::pvData::PVStructure::shared_pointer& pvReq)
|
||||
:requester(requester)
|
||||
:lock(lock)
|
||||
,requester(requester)
|
||||
,inoverflow(false)
|
||||
,running(false)
|
||||
,nbuffers(2)
|
||||
@@ -104,121 +107,136 @@ public:
|
||||
|
||||
//! Must call before first post(). Sets .complete and calls monitorConnect()
|
||||
//! @note that value will never by accessed except by post() and requestUpdate()
|
||||
void connect(const epics::pvData::PVStructurePtr& value)
|
||||
void connect(guard_t& guard, const epics::pvData::PVStructurePtr& value)
|
||||
{
|
||||
guard.assertIdenticalMutex(lock);
|
||||
epics::pvData::StructureConstPtr dtype(value->getStructure());
|
||||
epics::pvData::PVDataCreatePtr create(epics::pvData::getPVDataCreate());
|
||||
BaseMonitor::shared_pointer self(shared_from_this());
|
||||
requester_t::shared_pointer req(requester.lock());
|
||||
{
|
||||
guard_t G(lock);
|
||||
assert(!complete); // can't call twice
|
||||
|
||||
complete = value;
|
||||
empty.resize(nbuffers);
|
||||
for(size_t i=0; i<empty.size(); i++) {
|
||||
empty[i].reset(new epics::pvAccess::MonitorElement(create->createPVStructure(dtype)));
|
||||
}
|
||||
assert(!complete); // can't call twice
|
||||
|
||||
complete = value;
|
||||
empty.resize(nbuffers);
|
||||
for(size_t i=0; i<empty.size(); i++) {
|
||||
empty[i].reset(new epics::pvAccess::MonitorElement(create->createPVStructure(dtype)));
|
||||
}
|
||||
epics::pvData::Status sts;
|
||||
if(req)
|
||||
|
||||
if(req) {
|
||||
unguard_t U(guard);
|
||||
epics::pvData::Status sts;
|
||||
req->monitorConnect(sts, self, dtype);
|
||||
}
|
||||
}
|
||||
|
||||
struct no_overflow {};
|
||||
|
||||
//! post update if queue not full, if full return false w/o overflow
|
||||
bool post(const epics::pvData::BitSet& updated, no_overflow)
|
||||
bool post(guard_t& guard, const epics::pvData::BitSet& updated, no_overflow)
|
||||
{
|
||||
guard.assertIdenticalMutex(lock);
|
||||
requester_t::shared_pointer req;
|
||||
{
|
||||
guard_t G(lock);
|
||||
if(!complete || !running) return false;
|
||||
|
||||
changed |= updated;
|
||||
if(!complete || !running) return false;
|
||||
|
||||
if(empty.empty()) return false;
|
||||
changed |= updated;
|
||||
|
||||
if(p_postone())
|
||||
req = requester.lock();
|
||||
inoverflow = false;
|
||||
if(empty.empty()) return false;
|
||||
|
||||
if(p_postone())
|
||||
req = requester.lock();
|
||||
inoverflow = false;
|
||||
|
||||
if(req) {
|
||||
unguard_t U(guard);
|
||||
req->monitorEvent(shared_from_this());
|
||||
}
|
||||
if(req) req->monitorEvent(shared_from_this());
|
||||
return true;
|
||||
}
|
||||
|
||||
//! post update of pending changes. eg. call from requestUpdate()
|
||||
bool post()
|
||||
bool post(guard_t& guard)
|
||||
{
|
||||
guard.assertIdenticalMutex(lock);
|
||||
bool oflow;
|
||||
requester_t::shared_pointer req;
|
||||
{
|
||||
guard_t G(lock);
|
||||
if(!complete || !running) return false;
|
||||
|
||||
if(empty.empty()) {
|
||||
oflow = inoverflow = true;
|
||||
if(!complete || !running) return false;
|
||||
|
||||
} else {
|
||||
if(empty.empty()) {
|
||||
oflow = inoverflow = true;
|
||||
|
||||
if(p_postone())
|
||||
req = requester.lock();
|
||||
oflow = inoverflow = false;
|
||||
}
|
||||
} else {
|
||||
|
||||
if(p_postone())
|
||||
req = requester.lock();
|
||||
oflow = inoverflow = false;
|
||||
}
|
||||
|
||||
if(req) {
|
||||
unguard_t U(guard);
|
||||
req->monitorEvent(shared_from_this());
|
||||
}
|
||||
if(req) req->monitorEvent(shared_from_this());
|
||||
return !oflow;
|
||||
}
|
||||
|
||||
//! post update with changed and overflowed masks (eg. when updates were lost in some upstream queue)
|
||||
bool post(const epics::pvData::BitSet& updated, const epics::pvData::BitSet& overflowed)
|
||||
bool post(guard_t& guard,
|
||||
const epics::pvData::BitSet& updated,
|
||||
const epics::pvData::BitSet& overflowed)
|
||||
{
|
||||
guard.assertIdenticalMutex(lock);
|
||||
bool oflow;
|
||||
requester_t::shared_pointer req;
|
||||
{
|
||||
guard_t G(lock);
|
||||
if(!complete || !running) return false;
|
||||
|
||||
if(empty.empty()) {
|
||||
oflow = inoverflow = true;
|
||||
overflow |= overflowed;
|
||||
overflow.or_and(updated, changed);
|
||||
changed |= updated;
|
||||
if(!complete || !running) return false;
|
||||
|
||||
} else {
|
||||
if(empty.empty()) {
|
||||
oflow = inoverflow = true;
|
||||
overflow |= overflowed;
|
||||
overflow.or_and(updated, changed);
|
||||
changed |= updated;
|
||||
|
||||
changed |= updated;
|
||||
if(p_postone())
|
||||
req = requester.lock();
|
||||
oflow = inoverflow = false;
|
||||
}
|
||||
} else {
|
||||
|
||||
changed |= updated;
|
||||
if(p_postone())
|
||||
req = requester.lock();
|
||||
oflow = inoverflow = false;
|
||||
}
|
||||
|
||||
if(req) {
|
||||
unguard_t U(guard);
|
||||
req->monitorEvent(shared_from_this());
|
||||
}
|
||||
if(req) req->monitorEvent(shared_from_this());
|
||||
return !oflow;
|
||||
}
|
||||
|
||||
//! post update with changed
|
||||
bool post(const epics::pvData::BitSet& updated) {
|
||||
bool post(guard_t& guard, const epics::pvData::BitSet& updated) {
|
||||
bool oflow;
|
||||
requester_t::shared_pointer req;
|
||||
{
|
||||
guard_t G(lock);
|
||||
if(!complete || !running) return false;
|
||||
|
||||
if(empty.empty()) {
|
||||
oflow = inoverflow = true;
|
||||
overflow.or_and(updated, changed);
|
||||
changed |= updated;
|
||||
if(!complete || !running) return false;
|
||||
|
||||
} else {
|
||||
if(empty.empty()) {
|
||||
oflow = inoverflow = true;
|
||||
overflow.or_and(updated, changed);
|
||||
changed |= updated;
|
||||
|
||||
changed |= updated;
|
||||
if(p_postone())
|
||||
req = requester.lock();
|
||||
oflow = inoverflow = false;
|
||||
}
|
||||
} else {
|
||||
|
||||
changed |= updated;
|
||||
if(p_postone())
|
||||
req = requester.lock();
|
||||
oflow = inoverflow = false;
|
||||
}
|
||||
|
||||
if(req) {
|
||||
unguard_t U(guard);
|
||||
req->monitorEvent(shared_from_this());
|
||||
}
|
||||
if(req) req->monitorEvent(shared_from_this());
|
||||
return !oflow;
|
||||
}
|
||||
|
||||
@@ -250,7 +268,7 @@ public:
|
||||
virtual void onStop() {}
|
||||
//! called when within release() when the opportunity exists to end the overflow condition
|
||||
//! May do nothing, or lock and call post()
|
||||
virtual void requestUpdate() {guard_t G(lock); post();}
|
||||
virtual void requestUpdate() {guard_t G(lock); post(G);}
|
||||
|
||||
virtual void destroy()
|
||||
{
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#ifndef SB_H
|
||||
#define SB_H
|
||||
|
||||
#include <sstream>
|
||||
|
||||
// in-line string builder (eg. for exception messages)
|
||||
// throw std::runtime_error(SB()<<"Answer: !"<<42);
|
||||
struct SB {
|
||||
std::ostringstream strm;
|
||||
SB() {}
|
||||
operator std::string() const { return strm.str(); }
|
||||
template<typename T>
|
||||
SB& operator<<(T i) { strm<<i; return *this; }
|
||||
};
|
||||
|
||||
#endif // SB_H
|
||||
@@ -16,8 +16,6 @@
|
||||
#include "weakmap.h"
|
||||
#include "weakset.h"
|
||||
|
||||
#include <shareLib.h>
|
||||
|
||||
struct TestPV;
|
||||
struct TestPVChannel;
|
||||
struct TestPVMonitor;
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
# Module (source) version
|
||||
EPICS_QSRV_MAJOR_VERSION = 1
|
||||
EPICS_QSRV_MINOR_VERSION = 0
|
||||
EPICS_QSRV_MAINTENANCE_VERSION = 0
|
||||
EPICS_QSRV_DEVELOPMENT_FLAG = 0
|
||||
EPICS_QSRV_MINOR_VERSION = 2
|
||||
EPICS_QSRV_MAINTENANCE_VERSION = 4
|
||||
|
||||
# ABI version
|
||||
EPICS_QSRV_ABI_MAJOR_VERSION = 1
|
||||
EPICS_QSRV_ABI_MINOR_VERSION = 0
|
||||
EPICS_QSRV_ABI_MINOR_VERSION = 1
|
||||
|
||||
# Development flag, set to zero for release versions
|
||||
|
||||
EPICS_QSRV_DEVELOPMENT_FLAG = 0
|
||||
|
||||
# Immediately after a release the MAINTENANCE_VERSION
|
||||
# will be incremented and the DEVELOPMENT_FLAG set to 1
|
||||
|
||||
@@ -38,5 +38,8 @@ CHECK_RELEASE = YES
|
||||
# we don't actually use 'rset' explicitly, but quiet some warnings
|
||||
USR_CPPFLAGS += -DUSE_TYPED_RSET
|
||||
|
||||
# we use std::min()/max() and don't want conflicting MSVC defs
|
||||
USR_CPPFLAGS_WIN32 += -DNOMINMAX
|
||||
|
||||
-include $(TOP)/../CONFIG_SITE.local
|
||||
-include $(TOP)/configure/CONFIG_SITE.local
|
||||
|
||||
@@ -38,7 +38,7 @@ PROJECT_NAME = pva2pva
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 0
|
||||
PROJECT_NUMBER = 1.2.4
|
||||
|
||||
# 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
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
.wy-side-nav-search {
|
||||
background-color: #18334B;
|
||||
}
|
||||
|
||||
.wy-side-nav-search input[type="text"] {
|
||||
border-color: #18334b;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# http://www.sphinx-doc.org/en/master/config
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
# import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'EPICS Documentation'
|
||||
copyright = '2019, EPICS Controls.'
|
||||
author = 'EPICS'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.intersphinx',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
# Intersphinx links to subprojects
|
||||
intersphinx_mapping = {
|
||||
'how-tos': ('https://docs.epics-controls.org/projects/how-tos/en/latest', None),
|
||||
}
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
html_css_files = [
|
||||
'css/custom.css',
|
||||
]
|
||||
|
||||
master_doc = 'index'
|
||||
|
||||
html_theme_options = {
|
||||
'logo_only': True,
|
||||
}
|
||||
html_logo = "images/EPICS_white_logo_v02.png"
|
||||
|
||||
html_extra_path = ['../html']
|
||||
|
||||
|
||||
# -- Run Doxygen ------------------------------------------------------------
|
||||
|
||||
import subprocess
|
||||
subprocess.call('cd ..; mkdir -p html/doxygen; doxygen', shell=True)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
@@ -0,0 +1,16 @@
|
||||
pva2pva (QSRV / pvAccess Gateway)
|
||||
=================================
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
EPICS Website <https://epics-controls.org>
|
||||
EPICS Documentation Home <https://docs.epics-controls.org>
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: pva2pva
|
||||
|
||||
Reference Manual and API Documentation <https://docs.epics-controls.org/projects/pva2pva/en/latest/doxygen>
|
||||
Source Code Repository on GitHub <https://github.com/epics-base/pva2pva>
|
||||
@@ -1,15 +1,22 @@
|
||||
/**
|
||||
@mainpage pva2pva Home of QSRV and pvAccess 2 pvAccess gateway
|
||||
|
||||
- [Source](https://github.com/epics-base/pva2pva)
|
||||
- [Download](https://sourceforge.net/projects/epics-pvdata/files/)
|
||||
|
||||
@section qsrv QSRV
|
||||
|
||||
QSRV is a network server using the PVAccess protocol which
|
||||
@ref qsrv_page is a network server using the PVAccess protocol which
|
||||
runs inside an EPICS IOC process and allows clients
|
||||
to make requests to access the Process Variables (PVs)
|
||||
within.
|
||||
|
||||
Documentation of @ref qsrv_config including @ref qsrv_group_def ,
|
||||
@ref qsrv_aslib
|
||||
and @ref qsrv_link configuration.
|
||||
|
||||
- @ref release_notes
|
||||
|
||||
@subsection qsrv_build Building
|
||||
|
||||
To build the latest from version control
|
||||
@@ -0,0 +1,358 @@
|
||||
/**
|
||||
@page qsrv_page QSRV
|
||||
|
||||
@section qsrv_config QSRV Configuration
|
||||
|
||||
By default QSRV exposes all Process Variables (fields of process database records).
|
||||
In addition to these "single" PVs are special "group" PVs.
|
||||
|
||||
@subsection qsrv_single Single PVs
|
||||
|
||||
"single" PVs are the same set of names server by the Channel Access server (RSRV).
|
||||
This is all accessible record fields.
|
||||
So all data which is accessible via Channel Access is also accessible via PVAccess.
|
||||
|
||||
QSRV presents all "single" PVs as Structures conforming to the
|
||||
Normative Types NTScalar, NTScalarArray, or NTEnum depending on the native DBF field type.
|
||||
|
||||
@subsection qsrv_group_def Group PV definitions
|
||||
|
||||
A group is defined using a JSON syntax.
|
||||
Groups are defined with respect to a Group Name,
|
||||
which is also the PV name.
|
||||
So unlike records, the "field" of a group have a different meaning.
|
||||
Group field names are _not_ part of the PV name.
|
||||
|
||||
A group definition is split among several records.
|
||||
For example of a group including two records is:
|
||||
|
||||
@code
|
||||
record(ai, "rec:X") {
|
||||
info(Q:group, {
|
||||
"grp:name": {
|
||||
"X": {+channel:"VAL"}
|
||||
}
|
||||
})
|
||||
}
|
||||
record(ai, "rec:Y") {
|
||||
info(Q:group, {
|
||||
"grp:name": {
|
||||
"Y": {+channel:"VAL"}
|
||||
}
|
||||
})
|
||||
}
|
||||
@endcode
|
||||
|
||||
This group, named "grp:name", has two fields "X" and "Y".
|
||||
|
||||
@code
|
||||
$ pvget grp:name
|
||||
grp:name
|
||||
structure
|
||||
epics:nt/NTScalar:1.0 X
|
||||
double value 0
|
||||
alarm_t alarm INVALID DRIVER UDF
|
||||
time_t timeStamp <undefined> 0
|
||||
...
|
||||
epics:nt/NTScalar:1.0 Y
|
||||
double value 0
|
||||
alarm_t alarm INVALID DRIVER UDF
|
||||
time_t timeStamp <undefined> 0
|
||||
...
|
||||
@endcode
|
||||
|
||||
@subsection qsrv_group_ref Group PV reference
|
||||
|
||||
@code
|
||||
record(...) {
|
||||
info(Q:group, {
|
||||
"<group_name>":{
|
||||
+id:"some/NT:1.0", # top level ID
|
||||
+meta:"FLD", # map top level alarm/timeStamp
|
||||
+atomic:true, # whether monitors default to multi-locking atomicity
|
||||
"<field.name>":{
|
||||
+type:"scalar", # controls how map VAL mapped onto <field.name>
|
||||
+channel:"VAL",
|
||||
+id:"some/NT:1.0",
|
||||
+trigger:"*", # "*" or comma seperated list of <field.name>s
|
||||
+putorder:0, # set for fields where put is allowed, processing done in increasing order
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@endcode
|
||||
|
||||
@subsubsection qsrv_group_map_types Field mapping types
|
||||
|
||||
@li "scalar" or ""
|
||||
@li "plain"
|
||||
@li "any"
|
||||
@li "meta"
|
||||
@li "proc"
|
||||
|
||||
The "scalar" mapping places an NTScalar or NTScalarArray as a sub-structure.
|
||||
|
||||
The "plain" mapping ignores all meta-data and places only the "value" as a field.
|
||||
The "value" is equivalent to '.value' of the equivalent NTScalar/NTScalarArray as a field.
|
||||
|
||||
The "any" mapping places a variant union into which the "value" is placed.
|
||||
|
||||
The "meta" mapping ignores the "value" and places only the alarm and time
|
||||
meta-data as sub-fields.
|
||||
The special group level tag 'meta:""' allows these meta-data fields to be
|
||||
placed in the top-level structure.
|
||||
|
||||
The "proc" mapping uses neither "value" nor meta-data.
|
||||
Instead the target record is processed during a put.
|
||||
|
||||
@subsubsection qsrv_group_map_trig Field Update Triggers
|
||||
|
||||
The field triggers define how changes to the consitutent field
|
||||
are translated into a subscription update to the group.
|
||||
|
||||
The most use of these are "" which means that changes to the field
|
||||
are ignored, and do not result group update.
|
||||
And "*" which results in a group update containing the most recent
|
||||
values/meta-data of all fields.
|
||||
|
||||
It may be useful to specify a comma seperated list of field names
|
||||
so that changes may partially update the group.
|
||||
|
||||
@subsection qsrv_stamp QSRV Timestamp Options
|
||||
|
||||
QSRV has the ability to perform certain transformations on the timestamp before transporting it.
|
||||
The mechanism for configuring this is the "Q:time:tag" info() tag.
|
||||
|
||||
@subsubsection qsrv_stamp_nslsb Nano-seconds least significant bits
|
||||
|
||||
Setting "Q:time:tag" to a value of "nsec:lsb:#", where # is a number between 0 and 32,
|
||||
will split the nanoseconds value stored in the associated record.
|
||||
The least significant # bits are stored in the 'timeStamp.userTag' field.
|
||||
While the remaining 32-# bits are stored in 'timeStamp.nanoseconds' (without shifting).
|
||||
|
||||
For example, in the following situation 20 bits are split off into userTag.
|
||||
If the nanoseconds part of the record timestamp is 0x12345678,
|
||||
then the PVD structure would include "timeStamp.nanoseconds=0x12300000"
|
||||
and "timeStamp.userTag=0x45678".
|
||||
|
||||
@code
|
||||
record(ai, "...") {
|
||||
info(Q:time:tag, "nsec:lsb:20")
|
||||
}
|
||||
@endcode
|
||||
|
||||
@subsection qsrv_form QSRV Display Form Option
|
||||
|
||||
The value of the OPI display form hint ('display.form') can be set set with the "Q:form" info() tag.
|
||||
This hint, along with 'display.precision', is used by some OPI clients to control how values are rendered.
|
||||
|
||||
The text value of the tag must be one of the following choices.
|
||||
|
||||
@li Default
|
||||
@li String
|
||||
@li Binary
|
||||
@li Decimal
|
||||
@li Hex
|
||||
@li Exponential
|
||||
@li Engineering
|
||||
|
||||
@code
|
||||
record(ai, "...") {
|
||||
info(Q:form, "Default") # implied default
|
||||
}
|
||||
@endcode
|
||||
|
||||
@subsection qsrv_aslib Access Security
|
||||
|
||||
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,
|
||||
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).
|
||||
|
||||
Policy application differs from CA (RSRV) in several ways:
|
||||
|
||||
* Client hostname is always the numeric IP address. HAG() entries must either contained numeric IP addresses,
|
||||
or that asCheckClientIP=1 flag must be set to translate hostnames into IPs on ACF file load (effects CA server as well).
|
||||
This prevents clients from trivially forging "hostname".
|
||||
* In additional to client usernames. UAG definitions may contained items beginning with "role/" which are matched
|
||||
against the list of groups of which the client username is a member. Username to group lookup is done internally
|
||||
to QSRV, and depends on IOC host authentication configuration. Note that this is still based on the client provided
|
||||
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
|
||||
|
||||
When built against Base >= 3.16.1, support is enabled for PVAccess links,
|
||||
which are analogous to Channel Access (CA) links. However, the syntax
|
||||
for PVA links is quite different.
|
||||
|
||||
@note The "dbjlr" and "dbpvar" IOC shell command provide information about PVA links in a running IOC.
|
||||
|
||||
A simple configuration using defaults is
|
||||
|
||||
@code
|
||||
record(longin, "tgt") {}
|
||||
record(longin, "src") {
|
||||
field(INP, {pva:"tgt"})
|
||||
}
|
||||
@endcode
|
||||
|
||||
This is a shorthand for
|
||||
|
||||
@code
|
||||
record(longin, "tgt") {}
|
||||
record(longin, "src") {
|
||||
field(INP, {pva:{pv:"tgt"}})
|
||||
}
|
||||
@endcode
|
||||
|
||||
Some additional keys (beyond "pv") may be used.
|
||||
Defaults are shown below:
|
||||
|
||||
@code
|
||||
record(longin, "tgt") {}
|
||||
record(longin, "src") {
|
||||
field(INP, {pva:{
|
||||
pv:"tgt",
|
||||
field:"", # may be a sub-field
|
||||
local:false,# Require local PV
|
||||
Q:4, # monitor queue depth
|
||||
pipeline:false, # require that server uses monitor flow control protocol
|
||||
proc:none, # Request record processing (side-effects).
|
||||
sevr:false, # Maximize severity.
|
||||
time:false, # set record time during getValue
|
||||
monorder:0, # Order of record processing as a result of CP and CPP
|
||||
retry:false,# allow Put while disconnected.
|
||||
always:false,# CP/CPP input link process even when .value field hasn't changed
|
||||
defer:false # Defer put
|
||||
}})
|
||||
}
|
||||
@endcode
|
||||
|
||||
@subsubsection qsrv_link_pv pv: Target PV name
|
||||
|
||||
The PV name to search for.
|
||||
This is the same name which could be used with 'pvget' or other client tools.
|
||||
|
||||
@subsubsection qsrv_link_field field: Structure field name
|
||||
|
||||
The name of a sub-field of the remotely provided Structure.
|
||||
By default, an empty string "" uses the top-level Structure.
|
||||
|
||||
If the top level structure, or a sub-structure is selected, then
|
||||
it is expeccted to conform to NTScalar, NTScalarArray, or NTEnum
|
||||
to extract value and meta-data.
|
||||
|
||||
If the sub-field is an PVScalar or PVScalarArray, then a value
|
||||
will be taken from it, but not meta-data will be available.
|
||||
|
||||
@todo Ability to traverse through unions and into structure arrays (as with group mappings).
|
||||
|
||||
@subsubsection qsrv_link_local local: Require local PV
|
||||
|
||||
When true, link will not connect unless the named PV is provided by the local (QSRV) data provider.
|
||||
|
||||
@subsubsection qsrv_link_Q Q: Monitor queue depth
|
||||
|
||||
Requests a certain monitor queue depth.
|
||||
The server may, or may not, take this into consideration when selecting
|
||||
a queue depth.
|
||||
|
||||
@subsubsection qsrv_link_pipeline pipeline: Monitor flow control
|
||||
|
||||
Expect that the server supports PVA monitor flow control.
|
||||
If not, then the subscription will stall (ick.)
|
||||
|
||||
@subsubsection qsrv_link_proc proc: Request record processing (side-effects)
|
||||
|
||||
The meaning of this option depends on the direction of the link.
|
||||
|
||||
For output links, this option allows a request for remote processing (side-effects).
|
||||
|
||||
@li none (default) - Make no special request. Uses a server specific default.
|
||||
@li false, "NPP" - Request to skip processing.
|
||||
@li true, "PP" - Request to force processing.
|
||||
@li "CP", "CPP" - For output links, an alias for "PP".
|
||||
|
||||
For input links, this option controls whether the record containing
|
||||
the PVA link will be processed when subscription events are received.
|
||||
|
||||
@li none (default), false, "NPP" - Do not process on subscription updates.
|
||||
@li true, "CP" - Always process on subscription updates.
|
||||
@li "PP", "CPP" - Process on subscription updates if SCAN=Passive
|
||||
|
||||
@subsubsection qsrv_link_sevr sevr: Alarm propagation
|
||||
|
||||
This option controls whether reading a value from an input PVA link
|
||||
has the addition effect of propagating any alarm via the Maximize
|
||||
Severity process.
|
||||
|
||||
@li false - Do not maximize severity.
|
||||
@li true - Maximize alarm severity
|
||||
@li "MSI" - Maximize only if the remote severity is INVALID.
|
||||
|
||||
@subsubsection qsrv_link_time time: Time propagation
|
||||
|
||||
Somewhat analogous to sevr: applied to timestamp.
|
||||
When true, the record TIME field is updated when the link value is read.
|
||||
|
||||
@warning TSEL must be set to -2 for time:true to have an effect.
|
||||
|
||||
@subsubsection qsrv_link_monorder monorder: Monitor processing order
|
||||
|
||||
When multiple record target the same target PV, and request processing
|
||||
on subscription updates. This option allows the order of processing
|
||||
to be specified.
|
||||
|
||||
Record are processed in increasing order.
|
||||
monorder=-1 is processed before monorder=0.
|
||||
Both are processed before monorder=1.
|
||||
|
||||
@subsubsection qsrv_link_defer defer: Defer put
|
||||
|
||||
By default (defer=false) an output link will immediately
|
||||
start a PVA Put operation. defer=true will store the
|
||||
new value in an internal cache, but not start a PVA Put.
|
||||
|
||||
This option, in combination with field: allows a single
|
||||
Put to contain updates to multiple sub-fields.
|
||||
|
||||
|
||||
@subsubsection qsrv_link_retry retry: Put while disconnected
|
||||
|
||||
Allow a Put operation to be queued while the link is disconnected.
|
||||
The Put will be executed when the link becomes connected.
|
||||
|
||||
@subsubsection qsrv_link_always always: CP/CPP always process
|
||||
|
||||
By default (always:false) a subscription update will only cause a CP input link
|
||||
to scan if the structure field (cf. field: option) is marked as changed.
|
||||
Set to true to override this, and always process the link.
|
||||
|
||||
@subsubsection qsrv_link_sem Link semantics/behavior
|
||||
|
||||
This section attempts to answer some questions about how links behave in certain situations.
|
||||
|
||||
Links are evaluated in three basic contexts.
|
||||
|
||||
@li dbPutLink()/dbScanFwdLink()
|
||||
@li dbGetLink() of non-CP link
|
||||
@li dbGetLink() during a scan resulting from a CP link.
|
||||
|
||||
An input link can bring in a Value as well as meta-data, alarm, time, and display/control info.
|
||||
For input links, the PVA link engine attempts to always maintain consistency between Value, alarm, and time.
|
||||
However, consistency between these, and the display/control info is only ensured during a CP scan.
|
||||
|
||||
|
||||
|
||||
|
||||
*/
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
@page qsrv_page QSRV
|
||||
|
||||
@section qsrv_config QSRV Configuration
|
||||
|
||||
By default QSRV exposes all Process Variables (fields of process database records).
|
||||
In addition to these "single" PVs, special "group" PVs.
|
||||
|
||||
@subsection qsrv_group_sym Group PV semantics
|
||||
|
||||
@subsection qsrv_group_def Group PV definitions
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
|
||||
@page release_notes Release Notes
|
||||
|
||||
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)
|
||||
========================
|
||||
|
||||
- Changes
|
||||
- Enforce Access Security policy.
|
||||
- NTEnum .value field add missing "enum_t" type ID string.
|
||||
|
||||
Release 1.2.1 (July 2019)
|
||||
=========================
|
||||
|
||||
- Fixes
|
||||
- Fix unittest: testpvalink. No functional change.
|
||||
|
||||
Release 1.2.0 (Mar 2019)
|
||||
========================
|
||||
|
||||
- Incompatible changes
|
||||
- The field 'display.format' is replaced with 'display.form' and 'display.precision'.
|
||||
https://github.com/epics-base/pva2pva/issues/19
|
||||
- Additions
|
||||
- Use @code info(Q:form, "...") @endcode to set 'display.form'. See @ref qsrv_form
|
||||
- Fixes
|
||||
- Correct handling of 64-bit integer fields.
|
||||
- Install a copy of softIocExit.db for standalone builds
|
||||
|
||||
Release 1.1.0 (Nov 2018)
|
||||
==========================
|
||||
|
||||
- Incompatible changes
|
||||
- Requires pvDataCPP >= 7.1.0
|
||||
- Requires pvAccessCPP >= 6.1.0
|
||||
- Removals
|
||||
- Drop the broken ioccircle and ioccircle2 examples.
|
||||
- Fixes
|
||||
- Fix QSRV monitor locking causing crash
|
||||
- Fix Windows DLL import/export errors
|
||||
- Correctly handle empty "scalar" case of NELM=1, NORD=0.
|
||||
- Additions
|
||||
- QSRV implement channelList() (aka. 'pvlist') with list of record and group names.
|
||||
- @ref qsrv_link type (requires Base >= 3.16.1)
|
||||
|
||||
Release 1.0.0 (Dec 2017)
|
||||
========================
|
||||
|
||||
Initial Release
|
||||
|
||||
*/
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
# Bind gateway server side to this interface
|
||||
epicsEnvSet("EPICS_PVAS_INTF_ADDR_LIST","10.0.1.200")
|
||||
|
||||
# Gateway client side searches here. Must not include gateway server side interface
|
||||
epicsEnvSet("EPICS_PVA_ADDR_LIST", "10.1.1.255")
|
||||
# Prevent gateway client from automatically populating the address list,
|
||||
# which would include the gateway server side interface
|
||||
epicsEnvSet("EPICS_PVA_AUTO_ADDR_LIST","NO")
|
||||
|
||||
gwstart()
|
||||
|
||||
# PVA variables
|
||||
#
|
||||
# Server side
|
||||
#
|
||||
# EPICS_PVAS_INTF_ADDR_LIST - Bind to this interface for both UDP and TCP
|
||||
# EPICS_PVAS_SERVER_PORT - default TCP port
|
||||
# EPICS_PVAS_BROADCAST_PORT - Listen for searches on this port
|
||||
#
|
||||
# EPICS_PVA_SERVER_PORT - Unused if EPICS_PVAS_SERVER_PORT set
|
||||
#
|
||||
# Client side
|
||||
#
|
||||
# EPICS_PVA_BROADCAST_PORT - Default search port for *ADDR_LIST
|
||||
# EPICS_PVA_ADDR_LIST - Space seperated list of search endpoints (bcast or unicast)
|
||||
# EPICS_PVA_AUTO_ADDR_LIST - YES/NO whether to populate ADDR_LIST with all local interface bcast addrs
|
||||
@@ -1,66 +0,0 @@
|
||||
record(ao, "circle:step") {
|
||||
field(VAL , "1.0")
|
||||
field(DRVL, "0.0")
|
||||
field(DRVH, "359")
|
||||
field(PINI, "YES")
|
||||
}
|
||||
|
||||
record(ao, "circle:period") {
|
||||
field(VAL , "1.0")
|
||||
field(PINI, "YES")
|
||||
field(OUT , "circle:tick.ODLY NPP")
|
||||
}
|
||||
|
||||
record(calc, "circle:angle") {
|
||||
field(PINI, "RUNNING") # bootstrap
|
||||
field(INPA, "circle:angle NPP")
|
||||
field(INPB, "circle:step NPP")
|
||||
field(INPD, "360")
|
||||
field(DESC, "Angle")
|
||||
field(EGU , "deg")
|
||||
field(LOLO, "45")
|
||||
field(LOW , "135")
|
||||
field(HIGH, "225")
|
||||
field(HIHI, "315")
|
||||
field(LLSV, "MAJOR")
|
||||
field(LSV , "MINOR")
|
||||
field(HSV , "MINOR")
|
||||
field(HHSV, "MAJOR")
|
||||
field(CALC, "C:=A+B;(C>=D)?C-D:C")
|
||||
field(FLNK, "circle:x")
|
||||
field(PREC, "3")
|
||||
info(pdbGroup0, "circle|angle=VAL")
|
||||
info(pdbGroup1, "line|a=VAL")
|
||||
alias("line:a")
|
||||
}
|
||||
|
||||
record(calc, "circle:x") {
|
||||
field(INPA, "circle:angle NPP")
|
||||
field(CALC, "cos(A*PI/180)")
|
||||
field(TSEL, "circle:angle.TIME")
|
||||
field(FLNK, "circle:y")
|
||||
field(PREC, "3")
|
||||
info(pdbGroup, "circle|x=VAL")
|
||||
}
|
||||
|
||||
record(calc, "circle:y") {
|
||||
field(INPA, "circle:angle NPP")
|
||||
field(CALC, "sin(A*PI/180)")
|
||||
field(TSEL, "circle:angle.TIME")
|
||||
field(PREC, "3")
|
||||
field(FLNK, "line:b")
|
||||
info(pdbGroup, "circle|y=VAL")
|
||||
info(pdbTrigger,"circle|y>*")
|
||||
}
|
||||
|
||||
record(ai, "line:b") {
|
||||
field(INP, "line:a NPP")
|
||||
field(FLNK, "circle:tick")
|
||||
info(pdbGroup, "line|b=VAL")
|
||||
info(pdbTrigger,"line|b>*")
|
||||
}
|
||||
|
||||
record(calcout, "circle:tick") {
|
||||
field(ODLY, "1.0")
|
||||
field(OUT , "circle:angle.PROC CA") # loop
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#!../../bin/linux-x86_64-debug/softIocPVA
|
||||
|
||||
#epicsEnvSet("EPICS_PVA_ADDR_LIST", "10.5.2.255")
|
||||
#epicsEnvSet("EPICS_PVAS_INTF_ADDR_LIST","10.5.2.1")
|
||||
#epicsEnvSet("EPICS_PVA_AUTO_ADDR_LIST","NO")
|
||||
|
||||
#epicsEnvSet("EPICS_PVA_SERVER_PORT","5085")
|
||||
#epicsEnvSet("EPICS_PVA_BROADCAST_PORT","5086")
|
||||
#epicsEnvSet("EPICS_CA_SERVER_PORT","5067")
|
||||
#epicsEnvSet("EPICS_CA_REPEATER_PORT","5068")
|
||||
#epicsEnvSet("EPICS_CA_MAX_ARRAY_BYTES","13000000")
|
||||
|
||||
dbLoadRecords("circle.db","")
|
||||
|
||||
iocInit()
|
||||
@@ -1,5 +0,0 @@
|
||||
TOP = ../..
|
||||
include $(TOP)/configure/CONFIG
|
||||
ARCH = linux-x86_64-debug
|
||||
TARGETS = envPaths
|
||||
include $(TOP)/configure/RULES.ioc
|
||||
@@ -1,27 +0,0 @@
|
||||
record(calc, "circle2:x") {
|
||||
field(INPA, {pva:"circle.x CP MSI"})
|
||||
field(CALC, "2*A")
|
||||
field(FLNK, "circle2:y")
|
||||
info(pdbGroup, "circle2|x=VAL")
|
||||
# field(TPRO, "1")
|
||||
}
|
||||
|
||||
record(calc, "circle2:y") {
|
||||
field(INPA, {pva:"circle.y MSI"})
|
||||
field(CALC, "2*A")
|
||||
field(TSEL, "circle2:x.TIME")
|
||||
field(FLNK, "circle2:mag")
|
||||
info(pdbGroup, "circle2|y=VAL")
|
||||
# field(TPRO, "1")
|
||||
}
|
||||
|
||||
record(calc, "circle2:mag") {
|
||||
field(INPA, "circle2:x NPP MSI")
|
||||
field(INPB, "circle2:y NPP MSI")
|
||||
field(CALC, "SQRT(A*A+B*B)")
|
||||
field(MDEL, "-1")
|
||||
field(TSEL, "circle2:x.TIME")
|
||||
info(pdbGroup, "circle2|mag=VAL")
|
||||
info(pdbTrigger,"circle2|mag>*")
|
||||
# field(TPRO, "1")
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#!../../bin/linux-x86_64-debug/softIocPVA
|
||||
|
||||
#epicsEnvSet("EPICS_PVA_ADDR_LIST", "10.5.2.255")
|
||||
#epicsEnvSet("EPICS_PVAS_INTF_ADDR_LIST","10.5.2.1")
|
||||
#epicsEnvSet("EPICS_PVA_AUTO_ADDR_LIST","NO")
|
||||
|
||||
#epicsEnvSet("EPICS_PVA_SERVER_PORT","5085")
|
||||
#epicsEnvSet("EPICS_PVA_BROADCAST_PORT","5086")
|
||||
#epicsEnvSet("EPICS_CA_SERVER_PORT","5067")
|
||||
#epicsEnvSet("EPICS_CA_REPEATER_PORT","5068")
|
||||
epicsEnvSet("EPICS_CA_MAX_ARRAY_BYTES","13000000")
|
||||
|
||||
dbLoadRecords("circle2.db","")
|
||||
|
||||
var(pvaLinkDebug, 5)
|
||||
|
||||
iocInit()
|
||||
@@ -1,18 +1,20 @@
|
||||
|
||||
|
||||
record(longout, "$(N):ArraySize0_RBV") {
|
||||
field(VAL, "100")
|
||||
info(Q:group, {
|
||||
"$(N):Array":{
|
||||
"dimension[0].size":{+channel:"VAL", +type:"plain"}
|
||||
"dimension[0].size":{+channel:"VAL", +type:"plain", +putorder:0}
|
||||
}
|
||||
})
|
||||
field(FLNK, "$(N):ArraySize1_RBV")
|
||||
}
|
||||
|
||||
record(longout, "$(N):ArraySize1_RBV") {
|
||||
field(VAL, "100")
|
||||
info(Q:group, {
|
||||
"$(N):Array":{
|
||||
"dimension[1].size":{+channel:"VAL", +type:"plain"}
|
||||
"dimension[1].size":{+channel:"VAL", +type:"plain", +putorder:0}
|
||||
}
|
||||
})
|
||||
field(FLNK, "$(N):ArrayData_")
|
||||
@@ -20,6 +22,7 @@ record(longout, "$(N):ArraySize1_RBV") {
|
||||
|
||||
record(aSub, "$(N):ArrayData_") {
|
||||
field(SNAM, "QSRV_image_demo")
|
||||
field(PINI, "YES")
|
||||
field(FTA, "ULONG")
|
||||
field(FTB, "ULONG")
|
||||
field(FTVA, "USHORT")
|
||||
@@ -35,7 +38,9 @@ record(waveform, "$(N):ArrayData") {
|
||||
info(Q:group, {
|
||||
"$(N):Array":{
|
||||
+id:"epics:nt/NTNDArray:1.0",
|
||||
"value":{+type:"any", +channel:"VAL", +trigger:"*"},
|
||||
"value":{+type:"any",
|
||||
+channel:"VAL",
|
||||
+trigger:"*"},
|
||||
"":{+type:"meta", +channel:"SEVR"}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -35,11 +35,15 @@ record(aao, "$(N)B") {
|
||||
field(TPRO, "1")
|
||||
}
|
||||
|
||||
record(bo, "$(N)Save") {
|
||||
record(longout, "$(N)Save") {
|
||||
field(MDEL, "-1") # ensure we always trigger group monitor
|
||||
field(TPRO, "1")
|
||||
info(Q:group, {
|
||||
"$(N)Tbl":{
|
||||
"_save":{+type:"proc", +channel:"VAL", +putorder:2}
|
||||
"_save":{+type:"proc",
|
||||
+channel:"VAL",
|
||||
+putorder:2,
|
||||
+trigger:"*"}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
IOC for testing PVA links
|
||||
|
||||
Primary test is st.cmd. st2.cmd is run/stopped concurrently
|
||||
to test remote connect/disconnect. st2a.cmd to test type
|
||||
change.
|
||||
@@ -0,0 +1,106 @@
|
||||
record(ai, "$(P)target") {}
|
||||
|
||||
# local input link shorthand
|
||||
record(longin, "$(P)inp1") {
|
||||
field(INP , {pva:"$(P)target"})
|
||||
alias("$(P)inp1:alias")
|
||||
}
|
||||
|
||||
# local input link longhand
|
||||
record(longin, "$(P)inp2") {
|
||||
field(INP , {pva:{pv:"$(P)target"}})
|
||||
}
|
||||
|
||||
# remote input link
|
||||
record(longin, "$(P)inp3") {
|
||||
field(INP , {pva:"invalid:pv:name"})
|
||||
}
|
||||
|
||||
# null like (requires local, but not local)
|
||||
record(longin, "$(P)inp4") {
|
||||
field(INP , {pva:{pv:"invalid:pv:name", local:true}})
|
||||
}
|
||||
|
||||
# local input link w/ CP (process on update)
|
||||
record(calc, "$(P)cnt") {
|
||||
field(SCAN, "1 second")
|
||||
field(INPA, "$(P)cnt")
|
||||
field(INPB, "9")
|
||||
field(CALC, "A<B?A+1:0")
|
||||
field(HIGH, "7")
|
||||
field(HSV , "MAJOR")
|
||||
}
|
||||
record(longin, "$(P)track") {
|
||||
field(INP , {pva:{
|
||||
pv:"$(P)cnt",
|
||||
proc:"CP",
|
||||
sevr:true,
|
||||
time:true
|
||||
}})
|
||||
field(TSE, "-2")
|
||||
}
|
||||
|
||||
# remote input link w/ CP (process on update)
|
||||
record(longin, "$(P)track2") {
|
||||
field(INP , {pva:{
|
||||
pv:"$(P)cnt:rmt",
|
||||
proc:"CP",
|
||||
sevr:true,
|
||||
time:true
|
||||
}})
|
||||
field(TSE, "-2")
|
||||
}
|
||||
|
||||
|
||||
# local output link
|
||||
record(longout, "$(P)out1") {
|
||||
field(OUT , {pva:{pv:"$(P)out:tgt"}})
|
||||
field(TPRO, "1")
|
||||
}
|
||||
record(longout, "$(P)out:tgt") {
|
||||
field(TPRO, "1")
|
||||
}
|
||||
|
||||
# remote output link
|
||||
record(longout, "$(P)out2") {
|
||||
field(OUT , {pva:{pv:"$(P)out:rmt"}})
|
||||
field(TPRO, "1")
|
||||
}
|
||||
|
||||
# remote output link w/ retry (queue put while disconnected)
|
||||
record(longout, "$(P)out3") {
|
||||
field(OUT , {pva:{pv:"$(P)out:rmt", retry:true}})
|
||||
field(TPRO, "1")
|
||||
#info(base:lsetDebug, "YES")
|
||||
}
|
||||
|
||||
|
||||
# output link to waveform
|
||||
record(aao, "$(P)out4:wf") {
|
||||
field(FTVL, "DOUBLE")
|
||||
field(NELM, "10")
|
||||
field(OUT , {pva:{pv:"$(P)target:wf"}})
|
||||
}
|
||||
|
||||
# input link to waveform
|
||||
record(aao, "$(P)target:wf") {
|
||||
field(FTVL, "DOUBLE")
|
||||
field(NELM, "10")
|
||||
}
|
||||
|
||||
record(aai, "$(P)inp5:wf") {
|
||||
field(FTVL, "DOUBLE")
|
||||
field(NELM, "10")
|
||||
field(INP , {pva:{pv:"$(P)target:wf"}})
|
||||
}
|
||||
|
||||
|
||||
# test forward link
|
||||
record(calc, "$(P)fcnt1") {
|
||||
field(CALC, "VAL+1")
|
||||
field(FLNK, {pva:{pv:"$(P)fcnt2"}})
|
||||
info(base:lsetDebug, "YES")
|
||||
}
|
||||
record(calc, "$(P)fcnt2") {
|
||||
field(CALC, "VAL+1")
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# use with pvalink.db, run in a second IOC
|
||||
|
||||
record(calc, "$(P)cnt:rmt") {
|
||||
field(SCAN, "1 second")
|
||||
field(INPA, "$(P)cnt")
|
||||
field(INPB, "9")
|
||||
field(CALC, "A<B?A+0.1:0")
|
||||
field(HIGH, "7")
|
||||
field(HSV , "MAJOR")
|
||||
}
|
||||
|
||||
record(longout, "$(P)out:rmt") {
|
||||
field(TPRO, "1")
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# use with pvalink.db, run in a second IOC
|
||||
# use instead of pvalink2.db to cause a type change
|
||||
|
||||
record(calcout, "$(P)cnt:rmtx") {
|
||||
field(SCAN, "1 second")
|
||||
field(OUT , "$(P)cnt:rmt PP")
|
||||
field(INPA, "$(P)cnt")
|
||||
field(INPB, "9")
|
||||
field(CALC, "A<B?A+1:0")
|
||||
field(HIGH, "7")
|
||||
field(HSV , "MAJOR")
|
||||
}
|
||||
|
||||
record(longin, "$(P)cnt:rmt") {}
|
||||
|
||||
record(stringout, "$(P)out:rmt") {
|
||||
field(TPRO, "1")
|
||||
}
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!../../bin/linux-x86_64-debug/softIocPVA
|
||||
|
||||
dbLoadRecords("pvalink.db","P=TST:")
|
||||
|
||||
iocInit()
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!../../bin/linux-x86_64-debug/softIocPVA
|
||||
|
||||
dbLoadRecords("pvalink2.db","P=TST:")
|
||||
|
||||
iocInit()
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!../../bin/linux-x86_64-debug/softIocPVA
|
||||
|
||||
dbLoadRecords("pvalink2a.db","P=TST:")
|
||||
|
||||
iocInit()
|
||||
@@ -14,6 +14,7 @@
|
||||
*/
|
||||
{
|
||||
"version":1,
|
||||
"readOnly":false,
|
||||
"clients":[
|
||||
{
|
||||
"name":"theclient",
|
||||
@@ -29,6 +30,8 @@
|
||||
"name":"theserver",
|
||||
"clients":["theclient"],
|
||||
"interface":"127.0.0.1",
|
||||
"addrlist":"127.255.255.255",
|
||||
"autoaddrlist":false,
|
||||
"serverport":5075,
|
||||
"bcastport":5076
|
||||
}
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
#include "weakmap.h"
|
||||
#include "weakset.h"
|
||||
|
||||
#include <shareLib.h>
|
||||
|
||||
struct ChannelCache;
|
||||
struct ChannelCacheEntry;
|
||||
struct MonitorUser;
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
|
||||
#include <epicsAtomic.h>
|
||||
|
||||
#include <epicsTimer.h>
|
||||
#include <epicsMutex.h>
|
||||
#include <epicsGuard.h>
|
||||
#include <epicsEndian.h>
|
||||
|
||||
#include <pv/iocshelper.h>
|
||||
|
||||
#include <pv/pvAccess.h>
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
#include <pv/pvAccess.h>
|
||||
|
||||
#include <shareLib.h>
|
||||
|
||||
#include "chancache.h"
|
||||
|
||||
struct GWChannel : public epics::pvAccess::Channel
|
||||
|
||||
+57
-4
@@ -12,6 +12,8 @@
|
||||
#include <epicsStdlib.h>
|
||||
#include <epicsGetopt.h>
|
||||
#include <iocsh.h>
|
||||
#include <epicsTimer.h>
|
||||
#include <libComRegister.h>
|
||||
|
||||
#include <pv/json.h>
|
||||
|
||||
@@ -22,16 +24,21 @@
|
||||
#include <pv/reftrack.h>
|
||||
#include <pv/iocreftrack.h>
|
||||
#include <pv/iocshelper.h>
|
||||
#include <pv/logger.h>
|
||||
|
||||
#include "server.h"
|
||||
#include "pva2pva.h"
|
||||
|
||||
namespace pvd = epics::pvData;
|
||||
namespace pva = epics::pvAccess;
|
||||
|
||||
extern int p2pReadOnly;
|
||||
|
||||
namespace {
|
||||
|
||||
pvd::StructureConstPtr schema(pvd::getFieldCreate()->createFieldBuilder()
|
||||
->add("version", pvd::pvUInt)
|
||||
->add("readOnly", pvd::pvBoolean)
|
||||
->addNestedStructureArray("clients")
|
||||
->add("name", pvd::pvString)
|
||||
->add("provider", pvd::pvString)
|
||||
@@ -44,6 +51,8 @@ pvd::StructureConstPtr schema(pvd::getFieldCreate()->createFieldBuilder()
|
||||
->add("name", pvd::pvString)
|
||||
->addArray("clients", pvd::pvString)
|
||||
->add("interface", pvd::pvString)
|
||||
->add("addrlist", pvd::pvString)
|
||||
->add("autoaddrlist", pvd::pvBoolean)
|
||||
->add("serverport", pvd::pvUShort)
|
||||
->add("bcastport", pvd::pvUShort)
|
||||
->add("control_prefix", pvd::pvString)
|
||||
@@ -61,9 +70,12 @@ void getargs(ServerConfig& arg, int argc, char *argv[])
|
||||
int opt;
|
||||
bool checkonly = false;
|
||||
|
||||
while( (opt=getopt(argc, argv, "vhiIC"))!=-1)
|
||||
while( (opt=getopt(argc, argv, "qvhiIC"))!=-1)
|
||||
{
|
||||
switch(opt) {
|
||||
case 'q':
|
||||
arg.debug--;
|
||||
break;
|
||||
case 'v':
|
||||
arg.debug++;
|
||||
break;
|
||||
@@ -94,6 +106,8 @@ void getargs(ServerConfig& arg, int argc, char *argv[])
|
||||
std::ifstream strm(argv[optind]);
|
||||
pvd::parseJSON(strm, arg.conf);
|
||||
|
||||
p2pReadOnly = arg.conf->getSubFieldT<pvd::PVScalar>("readOnly")->getAs<pvd::boolean>();
|
||||
|
||||
unsigned version = arg.conf->getSubFieldT<pvd::PVUInt>("version")->get();
|
||||
if(version==0) {
|
||||
std::cerr<<"Warning: config file missing \"version\" key. Assuming 1\n";
|
||||
@@ -116,15 +130,19 @@ void getargs(ServerConfig& arg, int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
|
||||
GWServerChannelProvider::shared_pointer configure_client(const pvd::PVStructurePtr& conf)
|
||||
GWServerChannelProvider::shared_pointer configure_client(ServerConfig& arg, const pvd::PVStructurePtr& conf)
|
||||
{
|
||||
std::string name(conf->getSubFieldT<pvd::PVString>("name")->get());
|
||||
std::string provider(conf->getSubFieldT<pvd::PVString>("provider")->get());
|
||||
|
||||
LOG(pva::logLevelInfo, "Configure client '%s' with provider '%s'", name.c_str(), provider.c_str());
|
||||
|
||||
pva::Configuration::shared_pointer C(pva::ConfigurationBuilder()
|
||||
.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_BROADCAST_PORT", conf->getSubFieldT<pvd::PVScalar>("bcastport")->getAs<pvd::uint16>())
|
||||
.add("EPICS_PVA_DEBUG", arg.debug>=5 ? 5 : 0)
|
||||
.push_map()
|
||||
.build());
|
||||
|
||||
@@ -138,10 +156,17 @@ GWServerChannelProvider::shared_pointer configure_client(const pvd::PVStructureP
|
||||
|
||||
pva::ServerContext::shared_pointer configure_server(ServerConfig& arg, const pvd::PVStructurePtr& conf)
|
||||
{
|
||||
std::string name(conf->getSubFieldT<pvd::PVString>("name")->get());
|
||||
|
||||
LOG(pva::logLevelInfo, "Configure server '%s'", name.c_str());
|
||||
|
||||
pva::Configuration::shared_pointer C(pva::ConfigurationBuilder()
|
||||
.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_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_BROADCAST_PORT", conf->getSubFieldT<pvd::PVScalar>("bcastport")->getAs<pvd::uint16>())
|
||||
.add("EPICS_PVA_DEBUG", arg.debug>=5 ? 5 : 0)
|
||||
.push_map()
|
||||
.build());
|
||||
|
||||
@@ -221,6 +246,8 @@ int main(int argc, char *argv[])
|
||||
epics::iocshRegister<int, const char*, &gwsr>("gwsr", "level", "channel");
|
||||
epics::iocshRegister<int, const char*, const char*, &gwcr>("gwcr", "level", "client", "channel");
|
||||
|
||||
libComRegister();
|
||||
registerReadOnly();
|
||||
epics::registerRefCounter("ChannelCacheEntry", &ChannelCacheEntry::num_instances);
|
||||
epics::registerRefCounter("ChannelCacheEntry::CRequester", &ChannelCacheEntry::CRequester::num_instances);
|
||||
epics::registerRefCounter("GWChannel", &GWChannel::num_instances);
|
||||
@@ -231,6 +258,32 @@ int main(int argc, char *argv[])
|
||||
theserver = &arg;
|
||||
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;
|
||||
if(arg.debug<0)
|
||||
lvl = pva::logLevelError;
|
||||
else if(arg.debug==0)
|
||||
lvl = pva::logLevelWarn;
|
||||
else if(arg.debug==1)
|
||||
lvl = pva::logLevelInfo;
|
||||
else if(arg.debug==2)
|
||||
lvl = pva::logLevelDebug;
|
||||
else if(arg.debug==3)
|
||||
lvl = pva::logLevelTrace;
|
||||
else if(arg.debug>=4)
|
||||
lvl = pva::logLevelAll;
|
||||
SET_LOG_LEVEL(lvl);
|
||||
|
||||
pva::ClientFactory::start();
|
||||
|
||||
pvd::PVStructureArray::const_svector arr;
|
||||
@@ -249,7 +302,7 @@ int main(int argc, char *argv[])
|
||||
if(it!=arg.clients.end())
|
||||
throw std::runtime_error(std::string("Duplicate client name not allowed : ")+name);
|
||||
|
||||
arg.clients[name] = configure_client(client);
|
||||
arg.clients[name] = configure_client(arg, client);
|
||||
}
|
||||
|
||||
arr = arg.conf->getSubFieldT<pvd::PVStructureArray>("servers")->view();
|
||||
|
||||
+4
-6
@@ -5,14 +5,12 @@
|
||||
|
||||
#include <pv/pvAccess.h>
|
||||
|
||||
#include <shareLib.h>
|
||||
|
||||
typedef epicsGuard<epicsMutex> Guard;
|
||||
typedef epicsGuardRelease<epicsMutex> UnGuard;
|
||||
|
||||
epicsShareExtern void registerGWClientIocsh();
|
||||
epicsShareExtern void gwServerShutdown();
|
||||
epicsShareExtern void gwClientShutdown();
|
||||
epicsShareExtern void registerReadOnly();
|
||||
void registerGWClientIocsh();
|
||||
void gwServerShutdown();
|
||||
void gwClientShutdown();
|
||||
void registerReadOnly();
|
||||
|
||||
#endif // PVA2PVA_H
|
||||
|
||||
+5
-15
@@ -2,7 +2,9 @@
|
||||
|
||||
#include <epicsAtomic.h>
|
||||
#include <epicsString.h>
|
||||
#include <epicsTimer.h>
|
||||
|
||||
#include <pv/logger.h>
|
||||
#include <pv/pvIntrospect.h> /* for pvdVersion.h */
|
||||
#include <pv/epicsException.h>
|
||||
#include <pv/serverContext.h>
|
||||
@@ -39,14 +41,8 @@ GWServerChannelProvider::channelFind(std::string const & channelName,
|
||||
|
||||
if(!channelName.empty())
|
||||
{
|
||||
std::string newName;
|
||||
|
||||
// rewrite name
|
||||
newName = channelName;
|
||||
//newName[0] = 'y';
|
||||
|
||||
|
||||
ChannelCacheEntry::shared_pointer ent(cache.lookup(newName));
|
||||
LOG(pva::logLevelDebug, "Searching for '%s'", channelName.c_str());
|
||||
ChannelCacheEntry::shared_pointer ent(cache.lookup(channelName));
|
||||
if(ent) {
|
||||
found = true;
|
||||
ret = shared_from_this();
|
||||
@@ -68,19 +64,13 @@ GWServerChannelProvider::createChannel(std::string const & channelName,
|
||||
short priority, std::string const & addressx)
|
||||
{
|
||||
GWChannel::shared_pointer ret;
|
||||
std::string newName;
|
||||
std::string address = channelRequester->getRequesterName();
|
||||
|
||||
if(!channelName.empty())
|
||||
{
|
||||
|
||||
// rewrite name
|
||||
newName = channelName;
|
||||
//newName[0] = 'y';
|
||||
|
||||
Guard G(cache.cacheLock);
|
||||
|
||||
ChannelCacheEntry::shared_pointer ent(cache.lookup(newName)); // recursively locks cacheLock
|
||||
ChannelCacheEntry::shared_pointer ent(cache.lookup(channelName)); // recursively locks cacheLock
|
||||
|
||||
if(ent)
|
||||
{
|
||||
|
||||
+1
-3
@@ -6,8 +6,6 @@
|
||||
#include "chancache.h"
|
||||
#include "channel.h"
|
||||
|
||||
#include <shareLib.h>
|
||||
|
||||
struct GWServerChannelProvider :
|
||||
public epics::pvAccess::ChannelProvider,
|
||||
public epics::pvAccess::ChannelFind,
|
||||
@@ -48,7 +46,7 @@ struct ServerConfig {
|
||||
typedef std::map<std::string, epics::pvAccess::ServerContext::shared_pointer> servers_t;
|
||||
servers_t servers;
|
||||
|
||||
ServerConfig() :debug(0), interactive(true) {}
|
||||
ServerConfig() :debug(1), interactive(true) {}
|
||||
|
||||
void drop(const char *client, const char *channel);
|
||||
void status_server(int lvl, const char *server);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <pv/epicsException.h>
|
||||
#include <pv/monitor.h>
|
||||
#include <pv/thread.h>
|
||||
#include <pv/serverContext.h>
|
||||
|
||||
#include "server.h"
|
||||
|
||||
|
||||
+25
-25
@@ -75,7 +75,7 @@ private:
|
||||
mutex_type mutex;
|
||||
store_t store;
|
||||
};
|
||||
std::tr1::shared_ptr<data> m_data;
|
||||
std::tr1::shared_ptr<data> _data;
|
||||
|
||||
struct dtor {
|
||||
std::tr1::weak_ptr<data> container;
|
||||
@@ -110,7 +110,7 @@ private:
|
||||
};
|
||||
public:
|
||||
//! Construct a new empty set
|
||||
weak_value_map() :m_data(new data) {}
|
||||
weak_value_map() :_data(new data) {}
|
||||
|
||||
private:
|
||||
//! Not copyable
|
||||
@@ -122,22 +122,22 @@ public:
|
||||
//! exchange the two sets.
|
||||
//! @warning Not thread safe (exchanges mutexes as well)
|
||||
void swap(weak_value_map& O) {
|
||||
m_data.swap(O.m_data);
|
||||
_data.swap(O._data);
|
||||
}
|
||||
|
||||
//! Remove all (weak) entries from the set
|
||||
//! @note Thread safe
|
||||
void clear() {
|
||||
guard_type G(m_data->mutex);
|
||||
return m_data->store.clear();
|
||||
guard_type G(_data->mutex);
|
||||
return _data->store.clear();
|
||||
}
|
||||
|
||||
//! Test if set is empty at this moment
|
||||
//! @note Thread safe
|
||||
//! @warning see size()
|
||||
bool empty() const {
|
||||
guard_type G(m_data->mutex);
|
||||
return m_data->store.empty();
|
||||
guard_type G(_data->mutex);
|
||||
return _data->store.empty();
|
||||
}
|
||||
|
||||
//! number of entries in the set at this moment
|
||||
@@ -145,8 +145,8 @@ public:
|
||||
//! @warning May be momentarily inaccurate (larger) due to dead refs.
|
||||
//! which have not yet been removed.
|
||||
size_t size() const {
|
||||
guard_type G(m_data->mutex);
|
||||
return m_data->store.size();
|
||||
guard_type G(_data->mutex);
|
||||
return _data->store.size();
|
||||
}
|
||||
|
||||
//! proxy class for lookup of non-const
|
||||
@@ -167,8 +167,8 @@ public:
|
||||
{
|
||||
if(!v.unique())
|
||||
throw std::invalid_argument("Only unique() references may be inserted");
|
||||
value_pointer chainptr(v.get(), dtor(M.m_data, k, v));
|
||||
M.m_data->store[k] = chainptr;
|
||||
value_pointer chainptr(v.get(), dtor(M._data, k, v));
|
||||
M._data->store[k] = chainptr;
|
||||
v.swap(chainptr);
|
||||
return v;
|
||||
}
|
||||
@@ -215,9 +215,9 @@ public:
|
||||
value_pointer find(const K& k) const
|
||||
{
|
||||
value_pointer ret;
|
||||
guard_type G(m_data->mutex);
|
||||
typename store_t::const_iterator it(m_data->store.find(k));
|
||||
if(it!=m_data->store.end()) {
|
||||
guard_type G(_data->mutex);
|
||||
typename store_t::const_iterator it(_data->store.find(k));
|
||||
if(it!=_data->store.end()) {
|
||||
// may be nullptr if we race destruction
|
||||
// as ref. count falls to zero before we can remove it
|
||||
ret = it->second.lock();
|
||||
@@ -230,9 +230,9 @@ public:
|
||||
value_pointer insert(const K& k, value_pointer& v)
|
||||
{
|
||||
value_pointer ret;
|
||||
guard_type G(m_data->mutex);
|
||||
typename store_t::const_iterator it = m_data->store.find(k);
|
||||
if(it!=m_data->store.end())
|
||||
guard_type G(_data->mutex);
|
||||
typename store_t::const_iterator it = _data->store.find(k);
|
||||
if(it!=_data->store.end())
|
||||
ret = it->second.lock();
|
||||
(*this)[k] = v;
|
||||
return ret;
|
||||
@@ -243,9 +243,9 @@ public:
|
||||
lock_map_type lock_map() const
|
||||
{
|
||||
lock_map_type ret;
|
||||
guard_type G(m_data->mutex);
|
||||
for(typename store_t::const_iterator it = m_data->store.begin(),
|
||||
end = m_data->store.end(); it!=end; ++it)
|
||||
guard_type G(_data->mutex);
|
||||
for(typename store_t::const_iterator it = _data->store.begin(),
|
||||
end = _data->store.end(); it!=end; ++it)
|
||||
{
|
||||
value_pointer P(it->second.lock);
|
||||
if(P) ret[it->first] = P;
|
||||
@@ -259,10 +259,10 @@ public:
|
||||
lock_vector_type lock_vector() const
|
||||
{
|
||||
lock_vector_type ret;
|
||||
guard_type G(m_data->mutex);
|
||||
ret.reserve(m_data->store.size());
|
||||
for(typename store_t::const_iterator it = m_data->store.begin(),
|
||||
end = m_data->store.end(); it!=end; ++it)
|
||||
guard_type G(_data->mutex);
|
||||
ret.reserve(_data->store.size());
|
||||
for(typename store_t::const_iterator it = _data->store.begin(),
|
||||
end = _data->store.end(); it!=end; ++it)
|
||||
{
|
||||
value_pointer P(it->second.lock());
|
||||
if(P) ret.push_back(std::make_pair(it->first, P));
|
||||
@@ -274,7 +274,7 @@ public:
|
||||
//! for use with batch operations.
|
||||
//! @warning Use caution when swap()ing while holding this lock!
|
||||
inline epicsMutex& mutex() const {
|
||||
return m_data->mutex;
|
||||
return _data->mutex;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+25
-25
@@ -94,7 +94,7 @@ private:
|
||||
mutex_type mutex;
|
||||
store_t store;
|
||||
};
|
||||
std::tr1::shared_ptr<data> m_data;
|
||||
std::tr1::shared_ptr<data> _data;
|
||||
|
||||
//! Destroyer for a chained shared_ptr
|
||||
//! which holds the unique() real strong
|
||||
@@ -131,7 +131,7 @@ private:
|
||||
};
|
||||
public:
|
||||
//! Construct a new empty set
|
||||
weak_set() :m_data(new data) {}
|
||||
weak_set() :_data(new data) {}
|
||||
|
||||
private:
|
||||
//! Not copyable
|
||||
@@ -143,22 +143,22 @@ public:
|
||||
//! exchange the two sets.
|
||||
//! @warning Not thread safe (exchanges mutexes as well)
|
||||
void swap(weak_set& O) {
|
||||
m_data.swap(O.m_data);
|
||||
_data.swap(O._data);
|
||||
}
|
||||
|
||||
//! Remove all (weak) entries from the set
|
||||
//! @note Thread safe
|
||||
void clear() {
|
||||
guard_type G(m_data->mutex);
|
||||
return m_data->store.clear();
|
||||
guard_type G(_data->mutex);
|
||||
return _data->store.clear();
|
||||
}
|
||||
|
||||
//! Test if set is empty
|
||||
//! @note Thread safe
|
||||
//! @warning see size()
|
||||
bool empty() const {
|
||||
guard_type G(m_data->mutex);
|
||||
return m_data->store.empty();
|
||||
guard_type G(_data->mutex);
|
||||
return _data->store.empty();
|
||||
}
|
||||
|
||||
//! number of entries in the set at this moment
|
||||
@@ -166,8 +166,8 @@ public:
|
||||
//! @warning May be momentarily inaccurate (larger) due to dead refs.
|
||||
//! which have not yet been removed.
|
||||
size_t size() const {
|
||||
guard_type G(m_data->mutex);
|
||||
return m_data->store.size();
|
||||
guard_type G(_data->mutex);
|
||||
return _data->store.size();
|
||||
}
|
||||
|
||||
//! Insert a new entry into the set
|
||||
@@ -178,8 +178,8 @@ public:
|
||||
//! Remove any (weak) ref to this object from the set
|
||||
//! @returns the number of objects removed (0 or 1)
|
||||
size_t erase(value_pointer& v) {
|
||||
guard_type G(m_data->mutex);
|
||||
return m_data->store.erase(v);
|
||||
guard_type G(_data->mutex);
|
||||
return _data->store.erase(v);
|
||||
}
|
||||
|
||||
//! Return a set of strong references to all entries
|
||||
@@ -197,7 +197,7 @@ public:
|
||||
//! for use with batch operations.
|
||||
//! @warning Use caution when swap()ing while holding this lock!
|
||||
inline epicsMutex& mutex() const {
|
||||
return m_data->mutex;
|
||||
return _data->mutex;
|
||||
}
|
||||
|
||||
//! an iterator-ish object which also locks the set during iteration
|
||||
@@ -205,7 +205,7 @@ public:
|
||||
weak_set& set;
|
||||
epicsGuard<epicsMutex> guard;
|
||||
typename store_t::iterator it, end;
|
||||
XIterator(weak_set& S) :set(S), guard(S.mutex()), it(S.m_data->store.begin()), end(S.m_data->store.end()) {}
|
||||
XIterator(weak_set& S) :set(S), guard(S.mutex()), it(S._data->store.begin()), end(S._data->store.end()) {}
|
||||
//! yield the next live entry
|
||||
value_pointer next() {
|
||||
value_pointer ret;
|
||||
@@ -229,14 +229,14 @@ void weak_set<T>::insert(value_pointer &v)
|
||||
if(!v.unique())
|
||||
throw std::invalid_argument("Only unique() references may be inserted");
|
||||
|
||||
guard_type G(m_data->mutex);
|
||||
typename store_t::const_iterator it = m_data->store.find(v);
|
||||
if(it==m_data->store.end()) { // new object
|
||||
guard_type G(_data->mutex);
|
||||
typename store_t::const_iterator it = _data->store.find(v);
|
||||
if(it==_data->store.end()) { // new object
|
||||
|
||||
// wrapped strong ref. which removes from our map
|
||||
value_pointer chainptr(v.get(), dtor(m_data, v));
|
||||
value_pointer chainptr(v.get(), dtor(_data, v));
|
||||
|
||||
m_data->store.insert(chainptr);
|
||||
_data->store.insert(chainptr);
|
||||
|
||||
v.swap(chainptr); // we only keep the chained pointer
|
||||
} else {
|
||||
@@ -253,9 +253,9 @@ typename weak_set<T>::set_type
|
||||
weak_set<T>::lock_set() const
|
||||
{
|
||||
set_type ret;
|
||||
guard_type G(m_data->mutex);
|
||||
for(typename store_t::const_iterator it=m_data->store.begin(),
|
||||
end=m_data->store.end(); it!=end; ++it)
|
||||
guard_type G(_data->mutex);
|
||||
for(typename store_t::const_iterator it=_data->store.begin(),
|
||||
end=_data->store.end(); it!=end; ++it)
|
||||
{
|
||||
value_pointer P(it->lock());
|
||||
if(P) ret.insert(P);
|
||||
@@ -275,10 +275,10 @@ weak_set<T>::lock_vector() const
|
||||
template<typename T>
|
||||
void weak_set<T>::lock_vector(vector_type& ret) const
|
||||
{
|
||||
guard_type G(m_data->mutex);
|
||||
ret.reserve(m_data->store.size());
|
||||
for(typename store_t::const_iterator it=m_data->store.begin(),
|
||||
end=m_data->store.end(); it!=end; ++it)
|
||||
guard_type G(_data->mutex);
|
||||
ret.reserve(_data->store.size());
|
||||
for(typename store_t::const_iterator it=_data->store.begin(),
|
||||
end=_data->store.end(); it!=end; ++it)
|
||||
{
|
||||
value_pointer P(it->lock());
|
||||
if(P) ret.push_back(P);
|
||||
|
||||
+61
-2
@@ -5,23 +5,54 @@ include $(TOP)/configure/CONFIG
|
||||
# ADD MACRO DEFINITIONS AFTER THIS LINE
|
||||
#=============================
|
||||
|
||||
ifeq ($(OS_CLASS),RTEMS)
|
||||
# set by Base in configure/os/CONFIG.UnixCommon.Common
|
||||
# then clobbered by included RTEMS make/host.cfg
|
||||
# to drop the '-p'
|
||||
MKDIR = mkdir -p
|
||||
endif
|
||||
|
||||
LIBRARY += qsrv
|
||||
|
||||
SHRLIB_VERSION ?= $(EPICS_QSRV_ABI_MAJOR_VERSION).$(EPICS_QSRV_ABI_MINOR_VERSION)
|
||||
|
||||
USR_CPPFLAGS += -DQSRV_API_BUILDING
|
||||
USR_CPPFLAGS += -I$(TOP)/common -I$(TOP)/p2pApp
|
||||
|
||||
INC += pv/qsrv.h
|
||||
INC += pv/qsrvVersionNum.h
|
||||
|
||||
EXPANDVARS += EPICS_QSRV_MAJOR_VERSION
|
||||
EXPANDVARS += EPICS_QSRV_MINOR_VERSION
|
||||
EXPANDVARS += EPICS_QSRV_MAINTENANCE_VERSION
|
||||
EXPANDVARS += EPICS_QSRV_DEVELOPMENT_FLAG
|
||||
EXPANDVARS += EPICS_QSRV_ABI_MAJOR_VERSION
|
||||
EXPANDVARS += EPICS_QSRV_ABI_MINOR_VERSION
|
||||
|
||||
EXPANDFLAGS += $(foreach var,$(EXPANDVARS),-D$(var)="$(strip $($(var)))")
|
||||
|
||||
qsrv_SRCS += pvif.cpp
|
||||
qsrv_SRCS += qsrv.cpp
|
||||
qsrv_SRCS += pdb.cpp
|
||||
qsrv_SRCS += pdbsingle.cpp
|
||||
#qsrv_SRCS += pvalink.cpp
|
||||
qsrv_SRCS += demo.cpp
|
||||
qsrv_SRCS += imagedemo.c
|
||||
|
||||
ifdef BASE_3_16
|
||||
qsrv_SRCS += pdbgroup.cpp
|
||||
qsrv_SRCS += configparse.cpp
|
||||
|
||||
qsrv_SRCS += dbf_copy.cpp
|
||||
qsrv_SRCS += tpool.cpp
|
||||
|
||||
qsrv_SRCS += pvalink.cpp
|
||||
qsrv_SRCS += pvalink_lset.cpp
|
||||
qsrv_SRCS += pvalink_jlif.cpp
|
||||
qsrv_SRCS += pvalink_link.cpp
|
||||
qsrv_SRCS += pvalink_channel.cpp
|
||||
else
|
||||
|
||||
qsrv_SRCS += pvalink_null.cpp
|
||||
endif
|
||||
|
||||
qsrv_LIBS += pvAccess pvData
|
||||
@@ -29,7 +60,9 @@ qsrv_LIBS += $(EPICS_BASE_IOC_LIBS)
|
||||
|
||||
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
|
||||
|
||||
@@ -43,6 +76,11 @@ softIocPVA_LIBS += $(EPICS_BASE_IOC_LIBS)
|
||||
DBD += softIocPVA.dbd
|
||||
DBD += qsrv.dbd
|
||||
|
||||
ifneq ($(FINAL_LOCATION),$(EPICS_BASE))
|
||||
# Copy from Base if we won't be installed there
|
||||
DB_INSTALLS += $(EPICS_BASE)/db/softIocExit.db
|
||||
endif
|
||||
|
||||
softIocPVA_DBD += softIoc.dbd
|
||||
softIocPVA_DBD += PVAServerRegister.dbd
|
||||
softIocPVA_DBD += qsrv.dbd
|
||||
@@ -53,3 +91,24 @@ include $(TOP)/configure/RULES
|
||||
#----------------------------------------
|
||||
# ADD RULES AFTER THIS LINE
|
||||
|
||||
# Can't use EXPAND as generated headers must appear
|
||||
# in O.Common, but EXPAND emits rules for O.$(T_A)
|
||||
../O.Common/pv/qsrvVersionNum.h: ../pv/qsrvVersionNum.h@
|
||||
$(MKDIR) $(COMMON_DIR)/pv
|
||||
$(EXPAND_TOOL) $(EXPANDFLAGS) $($@_EXPANDFLAGS) $< $@
|
||||
|
||||
qsrv$(DEP): ../O.Common/pv/qsrvVersionNum.h
|
||||
pvalink$(DEP): ../O.Common/pv/qsrvVersionNum.h
|
||||
|
||||
ifdef BASE_3_16
|
||||
../O.Common/qsrv.dbd: ../qsrv-new.dbd
|
||||
$(CP) $< $@
|
||||
else
|
||||
../O.Common/qsrv.dbd: ../qsrv-old.dbd
|
||||
$(CP) $< $@
|
||||
endif
|
||||
qsrv.dbd$(DEP):
|
||||
@$(RM) $@
|
||||
@echo "$(COMMONDEP_TARGET): ../Makefile" > $@
|
||||
|
||||
../O.Common/softIocPVA.dbd: ../O.Common/qsrv.dbd
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
|
||||
#include <dbAccess.h>
|
||||
|
||||
#include <dbAccess.h>
|
||||
#include <dbChannel.h>
|
||||
#include <dbStaticLib.h>
|
||||
#include <dbEvent.h>
|
||||
#include <dbLock.h>
|
||||
|
||||
#include <pv/pvIntrospect.h>
|
||||
#include <pv/pvAccess.h>
|
||||
#include <pv/configuration.h>
|
||||
#include <pv/json.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include "pdbgroup.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
|
||||
#include <epicsStdio.h>
|
||||
#include <dbAccess.h>
|
||||
#include <dbChannel.h>
|
||||
#include <dbStaticLib.h>
|
||||
#include <dbLock.h>
|
||||
#include <dbEvent.h>
|
||||
#include <epicsString.h>
|
||||
#include <epicsVersion.h>
|
||||
|
||||
#include <pv/status.h>
|
||||
#include <pv/bitSet.h>
|
||||
#include <pv/pvData.h>
|
||||
#include <pv/anyscalar.h>
|
||||
|
||||
#include "pvif.h"
|
||||
|
||||
namespace pvd = epics::pvData;
|
||||
|
||||
// note that we handle DBF_ENUM differently than in pvif.cpp
|
||||
static
|
||||
pvd::ScalarType DBR2PVD(short dbr)
|
||||
{
|
||||
switch(dbr) {
|
||||
#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case DBR_##DBFTYPE: return pvd::pv##PVACODE;
|
||||
#define CASE_SKIP_BOOL
|
||||
#define CASE_REAL_INT64
|
||||
#include "pv/typemap.h"
|
||||
#undef CASE_SKIP_BOOL
|
||||
#undef CASE_REAL_INT64
|
||||
#undef CASE
|
||||
case DBF_ENUM: return pvd::pvUShort;
|
||||
case DBF_STRING: return pvd::pvString;
|
||||
}
|
||||
throw std::invalid_argument("Unsupported DBR code");
|
||||
}
|
||||
|
||||
long copyPVD2DBF(const pvd::PVField::const_shared_pointer& inraw,
|
||||
void *outbuf, short outdbf, long *outnReq)
|
||||
{
|
||||
long nreq = outnReq ? *outnReq : 1;
|
||||
if(!inraw || nreq <= 0 || INVALID_DB_REQ(outdbf)) return S_db_errArg;
|
||||
|
||||
pvd::ScalarType outpvd = DBR2PVD(outdbf);
|
||||
|
||||
pvd::PVField::const_shared_pointer in(inraw);
|
||||
|
||||
if(outdbf != DBF_STRING && in->getField()->getType() == pvd::structure) {
|
||||
// assume NTEnum.
|
||||
// index to string not requested, so attempt to treat .index as plain integer
|
||||
in = static_cast<const pvd::PVStructure*>(in.get())->getSubField("index");
|
||||
if(!in) return S_db_errArg;
|
||||
}
|
||||
|
||||
if(in->getField()->getType() == pvd::structure) {
|
||||
assert(outdbf == DBF_STRING);
|
||||
char *outsbuf = (char*)outbuf;
|
||||
|
||||
// maybe NTEnum
|
||||
// try index -> string
|
||||
const pvd::PVStructure* sin = static_cast<const pvd::PVStructure*>(in.get());
|
||||
|
||||
pvd::PVScalar::const_shared_pointer index(sin->getSubField<pvd::PVScalar>("index"));
|
||||
if(!index) return S_db_badField; // Not NTEnum, don't know how to handle...
|
||||
|
||||
// we will have an answer.
|
||||
if(outnReq)
|
||||
*outnReq = 1;
|
||||
|
||||
pvd::uint16 ival = index->getAs<pvd::uint16>();
|
||||
|
||||
|
||||
pvd::PVStringArray::const_shared_pointer choices(sin->getSubField<pvd::PVStringArray>("choices"));
|
||||
|
||||
if(choices) {
|
||||
pvd::PVStringArray::const_svector strs(choices->view());
|
||||
|
||||
if(ival < strs.size()) {
|
||||
// found it!
|
||||
|
||||
const std::string& sval = strs[ival];
|
||||
size_t slen = std::min(sval.size(), size_t(MAX_STRING_SIZE-1));
|
||||
memcpy(outbuf, sval.c_str(), slen);
|
||||
outsbuf[slen] = '\0';
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// didn't find it. either no choices or index is out of range
|
||||
|
||||
// print numeric index
|
||||
epicsSnprintf(outsbuf, MAX_STRING_SIZE, "%u", ival);
|
||||
return 0;
|
||||
|
||||
} else if(in->getField()->getType() == pvd::scalarArray) {
|
||||
const pvd::PVScalarArray* sarr = static_cast<const pvd::PVScalarArray*>(in.get());
|
||||
pvd::shared_vector<const void> arr;
|
||||
sarr->getAs(arr);
|
||||
size_t elemsize = pvd::ScalarTypeFunc::elementSize(arr.original_type());
|
||||
|
||||
arr.slice(0, nreq*elemsize);
|
||||
nreq = arr.size()/elemsize;
|
||||
|
||||
if(outdbf == DBF_STRING) {
|
||||
char *outsbuf = (char*)outbuf;
|
||||
|
||||
// allocate a temp buffer of string[], ick...
|
||||
pvd::shared_vector<std::string> strs(nreq); // alloc
|
||||
|
||||
pvd::castUnsafeV(nreq, pvd::pvString, strs.data(), arr.original_type(), arr.data());
|
||||
|
||||
for(long i =0; i<nreq; i++, outsbuf += MAX_STRING_SIZE) {
|
||||
size_t slen = std::min(strs[i].size(), size_t(MAX_STRING_SIZE-1));
|
||||
memcpy(outsbuf, strs[i].c_str(), slen);
|
||||
outsbuf[slen] = '\0';
|
||||
}
|
||||
|
||||
} else {
|
||||
pvd::castUnsafeV(nreq, outpvd, outbuf, arr.original_type(), arr.data());
|
||||
}
|
||||
|
||||
if(outnReq)
|
||||
*outnReq = nreq;
|
||||
return 0;
|
||||
|
||||
} else if(in->getField()->getType() == pvd::scalar) {
|
||||
char *outsbuf = (char*)outbuf;
|
||||
const pvd::PVScalar* sval = static_cast<const pvd::PVScalar*>(in.get());
|
||||
pvd::AnyScalar val;
|
||||
sval->getAs(val);
|
||||
|
||||
if(outdbf == DBF_STRING && val.type()==pvd::pvString) {
|
||||
// std::string to char*
|
||||
size_t len = std::min(val.as<std::string>().size(), size_t(MAX_STRING_SIZE-1));
|
||||
|
||||
memcpy(outbuf, val.as<std::string>().c_str(), len);
|
||||
outsbuf[len] = '\0';
|
||||
|
||||
} else if(outdbf == DBF_STRING) {
|
||||
// non-string to char*
|
||||
std::string temp;
|
||||
|
||||
pvd::castUnsafeV(1, pvd::pvString, &temp, val.type(), val.unsafe());
|
||||
|
||||
size_t len = std::min(temp.size(), size_t(MAX_STRING_SIZE-1));
|
||||
|
||||
memcpy(outbuf, temp.c_str(), len);
|
||||
outsbuf[len] = '\0';
|
||||
|
||||
} else {
|
||||
// non-string to any
|
||||
pvd::castUnsafeV(1, outpvd, outbuf, val.type(), val.unsafe());
|
||||
}
|
||||
|
||||
if(outnReq)
|
||||
*outnReq = 1;
|
||||
return 0;
|
||||
|
||||
} else {
|
||||
// struct array or other strangeness which I don't know how to handle
|
||||
return S_dbLib_badField;
|
||||
}
|
||||
}
|
||||
|
||||
long copyDBF2PVD(const pvd::shared_vector<const void> &inbuf,
|
||||
const pvd::PVField::shared_pointer& outraw,
|
||||
pvd::BitSet& changed,
|
||||
const pvd::PVStringArray::const_svector &choices)
|
||||
{
|
||||
|
||||
pvd::ScalarType inpvd = inbuf.original_type();
|
||||
size_t incnt = inbuf.size()/pvd::ScalarTypeFunc::elementSize(inpvd);
|
||||
|
||||
if(!outraw) return S_db_errArg;
|
||||
|
||||
pvd::PVField::shared_pointer out(outraw);
|
||||
|
||||
if(inpvd != pvd::pvString && out->getField()->getType() == pvd::structure) {
|
||||
// assume NTEnum.
|
||||
// string to index not requested, so attempt to treat .index as plain integer
|
||||
out = static_cast<pvd::PVStructure*>(out.get())->getSubField("index");
|
||||
if(!out) return S_db_errArg;
|
||||
}
|
||||
|
||||
if(out->getField()->getType() == pvd::structure) {
|
||||
assert(inpvd == pvd::pvString);
|
||||
|
||||
if(incnt==0)
|
||||
return S_db_errArg; // Need at least one string
|
||||
|
||||
const pvd::shared_vector<const std::string> insbuf(pvd::static_shared_vector_cast<const std::string>(inbuf));
|
||||
const std::string& instr(insbuf[0]);
|
||||
|
||||
// assume NTEnum
|
||||
// try string to index, then parse
|
||||
pvd::PVStructure* sout = static_cast<pvd::PVStructure*>(out.get());
|
||||
|
||||
pvd::PVScalar::shared_pointer index(sout->getSubField<pvd::PVScalar>("index"));
|
||||
if(!index) return S_db_badField; // Not NTEnum, don't know how to handle...
|
||||
|
||||
pvd::uint16 result = pvd::uint16(-1);
|
||||
bool match = false;
|
||||
|
||||
for(size_t i=0, N=std::min(size_t(0xffff), choices.size()); i<N; i++) {
|
||||
if(choices[i] == instr) {
|
||||
match = true;
|
||||
result = pvd::uint16(i);
|
||||
}
|
||||
}
|
||||
|
||||
if(!match) {
|
||||
// no choice string matched, so try to parse as integer
|
||||
|
||||
try{
|
||||
result = pvd::castUnsafe<pvd::uint16>(instr);
|
||||
}catch(std::exception&){
|
||||
return S_db_errArg;
|
||||
}
|
||||
}
|
||||
|
||||
index->putFrom(result);
|
||||
|
||||
out = index;
|
||||
|
||||
} else if(out->getField()->getType() == pvd::scalarArray) {
|
||||
pvd::PVScalarArray* sarr = static_cast<pvd::PVScalarArray*>(out.get());
|
||||
|
||||
sarr->putFrom(inbuf);
|
||||
|
||||
} else if(out->getField()->getType() == pvd::scalar) {
|
||||
pvd::PVScalar* sval = static_cast<pvd::PVScalar*>(out.get());
|
||||
|
||||
if(incnt==0) return S_db_errArg;
|
||||
|
||||
pvd::AnyScalar val(inpvd, inbuf.data());
|
||||
|
||||
sval->putFrom(val);
|
||||
|
||||
} else {
|
||||
// struct array or other strangeness which I don't know how to handle
|
||||
return S_db_badField;
|
||||
}
|
||||
|
||||
changed.set(out->getFieldOffset());
|
||||
return 0;
|
||||
}
|
||||
+31
-6
@@ -10,13 +10,15 @@
|
||||
#include <epicsStdio.h>
|
||||
|
||||
#include <dbAccess.h>
|
||||
#include <dbChannel.h>
|
||||
#include <dbStaticLib.h>
|
||||
#include <dbNotify.h>
|
||||
|
||||
#include <dbEvent.h>
|
||||
|
||||
#include <pv/pvAccess.h>
|
||||
#include <pv/configuration.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include "helper.h"
|
||||
#include "pdbsingle.h"
|
||||
#include "pvif.h"
|
||||
@@ -196,9 +198,9 @@ struct PDBProcessor
|
||||
GroupConfig conf;
|
||||
GroupConfig::parse(json, conf);
|
||||
if(!conf.warning.empty())
|
||||
fprintf(stderr, "%s: warning(s) from info(Q:group, ...\n%s", rec.record()->name, conf.warning.c_str());
|
||||
fprintf(stderr, "%s: warning(s) from info(Q:group, ...\n%s", rec.name(), conf.warning.c_str());
|
||||
|
||||
recbase = rec.record()->name;
|
||||
recbase = rec.name();
|
||||
recbase += ".";
|
||||
|
||||
for(GroupConfig::groups_t::const_iterator git=conf.groups.begin(), gend=conf.groups.end();
|
||||
@@ -417,6 +419,12 @@ PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_poin
|
||||
members_map[mem.pvfldname] = 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.builder = PTRMOVE(mem.builder);
|
||||
assert(info.builder.get());
|
||||
@@ -512,7 +520,8 @@ PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_poin
|
||||
info.pvif.reset(info.builder->attach(info.chan, pv->complete, info.attachment));
|
||||
|
||||
// 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()) {
|
||||
info.evt_VALUE.create(event_context, info.chan, &pdb_group_event, DBE_VALUE|DBE_ALARM);
|
||||
@@ -583,8 +592,24 @@ pva::ChannelFind::shared_pointer
|
||||
PDBProvider::channelList(pva::ChannelListRequester::shared_pointer const & requester)
|
||||
{
|
||||
pva::ChannelFind::shared_pointer ret;
|
||||
requester->channelListResult(pvd::Status(pvd::Status::STATUSTYPE_ERROR, "not supported"),
|
||||
ret, pvd::PVStringArray::const_svector(), true);
|
||||
pvd::PVStringArray::svector names;
|
||||
for(pdbRecordIterator rec; !rec.done(); rec.next())
|
||||
{
|
||||
names.push_back(rec.name());
|
||||
}
|
||||
{
|
||||
epicsGuard<epicsMutex> G(transient_pv_map.mutex());
|
||||
|
||||
for(persist_pv_map_t::const_iterator it=persist_pv_map.begin(), end=persist_pv_map.end();
|
||||
it != end; ++it)
|
||||
{
|
||||
names.push_back(it->first);
|
||||
}
|
||||
}
|
||||
// check for duplicates?
|
||||
requester->channelListResult(pvd::Status::Ok,
|
||||
shared_from_this(),
|
||||
pvd::freeze(names), false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
+17
-10
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "weakmap.h"
|
||||
|
||||
#include <shareLib.h>
|
||||
#include <pv/qsrv.h>
|
||||
|
||||
struct PDBProvider;
|
||||
|
||||
@@ -30,24 +30,31 @@ struct PDBPV
|
||||
virtual void show(int lvl) {}
|
||||
};
|
||||
|
||||
struct epicsShareClass PDBProvider : public epics::pvAccess::ChannelProvider,
|
||||
public std::tr1::enable_shared_from_this<PDBProvider>
|
||||
struct QSRV_API PDBProvider : public epics::pvAccess::ChannelProvider,
|
||||
public epics::pvAccess::ChannelFind,
|
||||
public std::tr1::enable_shared_from_this<PDBProvider>
|
||||
{
|
||||
POINTER_DEFINITIONS(PDBProvider);
|
||||
|
||||
explicit PDBProvider(const epics::pvAccess::Configuration::const_shared_pointer& =epics::pvAccess::Configuration::const_shared_pointer());
|
||||
virtual ~PDBProvider();
|
||||
virtual void destroy();
|
||||
virtual std::string getProviderName();
|
||||
|
||||
// ChannelProvider
|
||||
virtual void destroy() OVERRIDE FINAL;
|
||||
virtual std::string getProviderName() OVERRIDE FINAL;
|
||||
virtual epics::pvAccess::ChannelFind::shared_pointer channelFind(std::string const & channelName,
|
||||
epics::pvAccess::ChannelFindRequester::shared_pointer const & channelFindRequester);
|
||||
virtual epics::pvAccess::ChannelFind::shared_pointer channelList(epics::pvAccess::ChannelListRequester::shared_pointer const & channelListRequester);
|
||||
epics::pvAccess::ChannelFindRequester::shared_pointer const & channelFindRequester) OVERRIDE FINAL;
|
||||
virtual epics::pvAccess::ChannelFind::shared_pointer channelList(epics::pvAccess::ChannelListRequester::shared_pointer const & channelListRequester) OVERRIDE FINAL;
|
||||
virtual epics::pvAccess::Channel::shared_pointer createChannel(std::string const & channelName,
|
||||
epics::pvAccess::ChannelRequester::shared_pointer const & channelRequester,
|
||||
short priority = PRIORITY_DEFAULT);
|
||||
short priority = PRIORITY_DEFAULT) OVERRIDE FINAL;
|
||||
virtual epics::pvAccess::Channel::shared_pointer createChannel(std::string const & channelName,
|
||||
epics::pvAccess::ChannelRequester::shared_pointer const & channelRequester,
|
||||
short priority, std::string const & address);
|
||||
short priority, std::string const & address) OVERRIDE FINAL;
|
||||
|
||||
// ChannelFind
|
||||
virtual std::tr1::shared_ptr<ChannelProvider> getChannelProvider() OVERRIDE FINAL { return shared_from_this(); }
|
||||
virtual void cancel() OVERRIDE FINAL {/* our channelFind() is synchronous, so nothing to cancel */}
|
||||
|
||||
typedef std::map<std::string, PDBPV::shared_pointer> persist_pv_map_t;
|
||||
persist_pv_map_t persist_pv_map;
|
||||
@@ -60,7 +67,7 @@ struct epicsShareClass PDBProvider : public epics::pvAccess::ChannelProvider,
|
||||
static size_t num_instances;
|
||||
};
|
||||
|
||||
epicsShareFunc
|
||||
QSRV_API
|
||||
void QSRVRegistrar_counters();
|
||||
|
||||
#endif // PDB_H
|
||||
|
||||
+89
-82
@@ -6,8 +6,13 @@
|
||||
|
||||
#include <epicsAtomic.h>
|
||||
#include <dbAccess.h>
|
||||
#include <dbChannel.h>
|
||||
#include <dbStaticLib.h>
|
||||
|
||||
#include <pv/pvAccess.h>
|
||||
#include <pv/configuration.h>
|
||||
#include <pv/epicsException.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include "helper.h"
|
||||
#include "pdbgroup.h"
|
||||
#include "pdb.h"
|
||||
@@ -31,30 +36,10 @@ void pdb_group_event(void *user_arg, struct dbChannel *chan,
|
||||
PDBGroupPV::shared_pointer self(std::tr1::static_pointer_cast<PDBGroupPV>(((PDBGroupPV*)evt->self)->shared_from_this()));
|
||||
PDBGroupPV::Info& info = self->members[idx];
|
||||
|
||||
PDBGroupPV::interested_remove_t temp;
|
||||
{
|
||||
bool doPost;
|
||||
{
|
||||
Guard G(self->lock);
|
||||
|
||||
if(!(evt->dbe_mask&DBE_PROPERTY)) {
|
||||
if(!info.had_initial_VALUE) {
|
||||
info.had_initial_VALUE = true;
|
||||
assert(self->initial_waits>0);
|
||||
self->initial_waits--;
|
||||
}
|
||||
} else {
|
||||
if(!info.had_initial_PROPERTY) {
|
||||
info.had_initial_PROPERTY = true;
|
||||
assert(self->initial_waits>0);
|
||||
self->initial_waits--;
|
||||
}
|
||||
}
|
||||
|
||||
doPost = self->initial_waits==0;
|
||||
|
||||
if(doPost)
|
||||
self->interested_iterating = true;
|
||||
}
|
||||
Guard G(self->lock);
|
||||
|
||||
self->scratch.clear();
|
||||
if(evt->dbe_mask&DBE_PROPERTY || !self->monatomic)
|
||||
@@ -75,35 +60,50 @@ void pdb_group_event(void *user_arg, struct dbChannel *chan,
|
||||
}
|
||||
}
|
||||
|
||||
if(!doPost) return; // don't post() until all subscriptions get initial updates
|
||||
|
||||
FOREACH(PDBGroupPV::interested_t::const_iterator, it, end, self->interested) {
|
||||
PDBGroupMonitor& mon = **it;
|
||||
mon.post(self->scratch);
|
||||
if(!(evt->dbe_mask&DBE_PROPERTY)) {
|
||||
if(!info.had_initial_VALUE) {
|
||||
info.had_initial_VALUE = true;
|
||||
assert(self->initial_waits>0);
|
||||
self->initial_waits--;
|
||||
}
|
||||
} else {
|
||||
if(!info.had_initial_PROPERTY) {
|
||||
info.had_initial_PROPERTY = true;
|
||||
assert(self->initial_waits>0);
|
||||
self->initial_waits--;
|
||||
}
|
||||
}
|
||||
|
||||
PDBGroupPV::interested_remove_t temp;
|
||||
{
|
||||
Guard G(self->lock);
|
||||
if(self->initial_waits==0) {
|
||||
self->interested_iterating = true;
|
||||
|
||||
assert(self->interested_iterating);
|
||||
|
||||
while(!self->interested_add.empty()) {
|
||||
PDBGroupPV::interested_t::iterator first(self->interested_add.begin());
|
||||
self->interested.insert(*first);
|
||||
self->interested_add.erase(first);
|
||||
FOREACH(PDBGroupPV::interested_t::const_iterator, it, end, self->interested) {
|
||||
PDBGroupMonitor& mon = **it;
|
||||
mon.post(G, self->scratch); // G unlocked
|
||||
}
|
||||
|
||||
temp.swap(self->interested_remove);
|
||||
for(PDBGroupPV::interested_remove_t::iterator it(temp.begin()),
|
||||
end(temp.end()); it != end; ++it)
|
||||
{
|
||||
self->interested.erase(static_cast<PDBGroupMonitor*>(it->get()));
|
||||
Guard G(self->lock);
|
||||
|
||||
assert(self->interested_iterating);
|
||||
|
||||
while(!self->interested_add.empty()) {
|
||||
PDBGroupPV::interested_t::iterator first(self->interested_add.begin());
|
||||
self->interested.insert(*first);
|
||||
self->interested_add.erase(first);
|
||||
}
|
||||
|
||||
temp.swap(self->interested_remove);
|
||||
for(PDBGroupPV::interested_remove_t::iterator it(temp.begin()),
|
||||
end(temp.end()); it != end; ++it)
|
||||
{
|
||||
self->interested.erase(static_cast<PDBGroupMonitor*>(it->get()));
|
||||
}
|
||||
|
||||
self->interested_iterating = false;
|
||||
|
||||
self->finalizeMonitor();
|
||||
}
|
||||
|
||||
self->interested_iterating = false;
|
||||
|
||||
self->finalizeMonitor();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,51 +138,55 @@ PDBGroupPV::connect(const std::tr1::shared_ptr<PDBProvider>& prov,
|
||||
const pva::ChannelRequester::shared_pointer& req)
|
||||
{
|
||||
PDBGroupChannel::shared_pointer ret(new PDBGroupChannel(shared_from_this(), prov, req));
|
||||
|
||||
ret->cred.update(req);
|
||||
|
||||
ret->aspvt.resize(members.size());
|
||||
for(size_t i=0, N=members.size(); i<N; i++)
|
||||
{
|
||||
ret->aspvt[i].add(members[i].chan, ret->cred);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// caller must not hold lock
|
||||
void PDBGroupPV::addMonitor(PDBGroupMonitor *mon)
|
||||
{
|
||||
bool needpost = false;
|
||||
{
|
||||
Guard G(lock);
|
||||
if(interested.empty() && interested_add.empty()) {
|
||||
// first monitor
|
||||
// start subscriptions
|
||||
Guard G(lock);
|
||||
if(interested.empty() && interested_add.empty()) {
|
||||
// first monitor
|
||||
// start subscriptions
|
||||
|
||||
size_t ievts = 0;
|
||||
for(size_t i=0; i<members.size(); i++) {
|
||||
PDBGroupPV::Info& info = members[i];
|
||||
size_t ievts = 0;
|
||||
for(size_t i=0; i<members.size(); i++) {
|
||||
PDBGroupPV::Info& info = members[i];
|
||||
|
||||
if(!!info.evt_VALUE) {
|
||||
db_event_enable(info.evt_VALUE.subscript);
|
||||
db_post_single_event(info.evt_VALUE.subscript);
|
||||
ievts++;
|
||||
info.had_initial_VALUE = false;
|
||||
} else {
|
||||
info.had_initial_VALUE = true;
|
||||
}
|
||||
assert(info.evt_PROPERTY.subscript);
|
||||
db_event_enable(info.evt_PROPERTY.subscript);
|
||||
db_post_single_event(info.evt_PROPERTY.subscript);
|
||||
if(!!info.evt_VALUE) {
|
||||
db_event_enable(info.evt_VALUE.subscript);
|
||||
db_post_single_event(info.evt_VALUE.subscript);
|
||||
ievts++;
|
||||
info.had_initial_PROPERTY = false;
|
||||
info.had_initial_VALUE = false;
|
||||
} else {
|
||||
info.had_initial_VALUE = true;
|
||||
}
|
||||
initial_waits = ievts;
|
||||
assert(info.evt_PROPERTY.subscript);
|
||||
db_event_enable(info.evt_PROPERTY.subscript);
|
||||
db_post_single_event(info.evt_PROPERTY.subscript);
|
||||
ievts++;
|
||||
info.had_initial_PROPERTY = false;
|
||||
}
|
||||
initial_waits = ievts;
|
||||
|
||||
} else if(initial_waits==0) {
|
||||
// new subscriber and already had initial update
|
||||
needpost = true;
|
||||
} // else new subscriber, but no initial update. so just wait
|
||||
} else if(initial_waits==0) {
|
||||
// new subscriber and already had initial update
|
||||
mon->post(G);
|
||||
} // else new subscriber, but no initial update. so just wait
|
||||
|
||||
if(interested_iterating)
|
||||
interested_add.insert(mon);
|
||||
else
|
||||
interested.insert(mon);
|
||||
}
|
||||
if(needpost)
|
||||
mon->post();
|
||||
if(interested_iterating)
|
||||
interested_add.insert(mon);
|
||||
else
|
||||
interested.insert(mon);
|
||||
}
|
||||
|
||||
// caller must not hold lock
|
||||
@@ -280,7 +284,8 @@ PDBGroupChannel::createMonitor(
|
||||
PDBGroupMonitor::shared_pointer ret(new PDBGroupMonitor(pv->shared_from_this(), requester, pvRequest));
|
||||
ret->weakself = ret;
|
||||
assert(!!pv->complete);
|
||||
ret->connect(pv->complete);
|
||||
guard_t G(pv->lock);
|
||||
ret->connect(G, pv->complete);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -371,7 +376,9 @@ void PDBGroupPut::put(pvd::PVStructure::shared_pointer const & value,
|
||||
|
||||
DBScanLocker L(dbChannelRecord(info.chan));
|
||||
|
||||
ret |= putpvif[i]->get(*changed, info.allowProc ? doProc : PVIF::ProcInhibit);
|
||||
ret |= putpvif[i]->get(*changed,
|
||||
info.allowProc ? doProc : PVIF::ProcInhibit,
|
||||
channel->aspvt[i].canWrite());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,7 +418,7 @@ void PDBGroupPut::get()
|
||||
PDBGroupMonitor::PDBGroupMonitor(const PDBGroupPV::shared_pointer& pv,
|
||||
const epics::pvAccess::MonitorRequester::weak_pointer &requester,
|
||||
const pvd::PVStructure::shared_pointer& pvReq)
|
||||
:BaseMonitor(requester, pvReq)
|
||||
:BaseMonitor(pv->lock, requester, pvReq)
|
||||
,pv(pv)
|
||||
{
|
||||
epics::atomic::increment(num_instances);
|
||||
@@ -446,5 +453,5 @@ void PDBGroupMonitor::onStop()
|
||||
void PDBGroupMonitor::requestUpdate()
|
||||
{
|
||||
Guard G(pv->lock);
|
||||
post();
|
||||
post(G);
|
||||
}
|
||||
|
||||
+24
-23
@@ -17,11 +17,9 @@
|
||||
#include "pvif.h"
|
||||
#include "pdb.h"
|
||||
|
||||
#include <shareLib.h>
|
||||
|
||||
struct epicsShareClass GroupConfig
|
||||
struct QSRV_API GroupConfig
|
||||
{
|
||||
struct epicsShareClass Field {
|
||||
struct QSRV_API Field {
|
||||
std::string type, channel, trigger, id;
|
||||
int putorder;
|
||||
|
||||
@@ -36,7 +34,7 @@ struct epicsShareClass GroupConfig
|
||||
}
|
||||
};
|
||||
|
||||
struct epicsShareClass Group {
|
||||
struct QSRV_API Group {
|
||||
typedef std::map<std::string, Field> fields_t;
|
||||
fields_t fields;
|
||||
bool atomic, atomic_set;
|
||||
@@ -70,7 +68,7 @@ struct PDBGroupMonitor;
|
||||
void pdb_group_event(void *user_arg, struct dbChannel *chan,
|
||||
int eventsRemaining, struct db_field_log *pfl);
|
||||
|
||||
struct epicsShareClass PDBGroupPV : public PDBPV
|
||||
struct QSRV_API PDBGroupPV : public PDBPV
|
||||
{
|
||||
POINTER_DEFINITIONS(PDBGroupPV);
|
||||
weak_pointer weakself;
|
||||
@@ -89,6 +87,8 @@ struct epicsShareClass PDBGroupPV : public PDBPV
|
||||
|
||||
struct Info {
|
||||
DBCH chan;
|
||||
// used for DBE_PROPERTY subscription when chan has filters
|
||||
DBCH chan2;
|
||||
std::tr1::shared_ptr<PVIFBuilder> builder;
|
||||
FieldName attachment;
|
||||
typedef std::vector<size_t> triggers_t;
|
||||
@@ -124,7 +124,7 @@ struct epicsShareClass PDBGroupPV : public PDBPV
|
||||
virtual
|
||||
epics::pvAccess::Channel::shared_pointer
|
||||
connect(const std::tr1::shared_ptr<PDBProvider>& prov,
|
||||
const epics::pvAccess::ChannelRequester::shared_pointer& req) OVERRIDE;
|
||||
const epics::pvAccess::ChannelRequester::shared_pointer& req) OVERRIDE FINAL;
|
||||
|
||||
void addMonitor(PDBGroupMonitor*);
|
||||
void removeMonitor(PDBGroupMonitor*);
|
||||
@@ -133,12 +133,15 @@ struct epicsShareClass PDBGroupPV : public PDBPV
|
||||
virtual void show(int lvl) OVERRIDE;
|
||||
};
|
||||
|
||||
struct epicsShareClass PDBGroupChannel : public BaseChannel,
|
||||
struct QSRV_API PDBGroupChannel : public BaseChannel,
|
||||
public std::tr1::enable_shared_from_this<PDBGroupChannel>
|
||||
{
|
||||
POINTER_DEFINITIONS(PDBGroupChannel);
|
||||
|
||||
PDBGroupPV::shared_pointer pv;
|
||||
std::vector<ASCLIENT> aspvt;
|
||||
// storage referenced from aspvt
|
||||
ASCred cred;
|
||||
|
||||
static size_t num_instances;
|
||||
|
||||
@@ -149,12 +152,12 @@ struct epicsShareClass PDBGroupChannel : public BaseChannel,
|
||||
|
||||
virtual epics::pvAccess::ChannelPut::shared_pointer createChannelPut(
|
||||
epics::pvAccess::ChannelPutRequester::shared_pointer const & requester,
|
||||
epics::pvData::PVStructure::shared_pointer const & pvRequest);
|
||||
epics::pvData::PVStructure::shared_pointer const & pvRequest) OVERRIDE FINAL;
|
||||
virtual epics::pvData::Monitor::shared_pointer createMonitor(
|
||||
epics::pvData::MonitorRequester::shared_pointer const & requester,
|
||||
epics::pvData::PVStructure::shared_pointer const & pvRequest);
|
||||
epics::pvData::PVStructure::shared_pointer const & pvRequest) OVERRIDE FINAL;
|
||||
|
||||
virtual void printInfo(std::ostream& out);
|
||||
virtual void printInfo(std::ostream& out) OVERRIDE FINAL;
|
||||
};
|
||||
|
||||
struct PDBGroupPut : public epics::pvAccess::ChannelPut,
|
||||
@@ -180,16 +183,14 @@ struct PDBGroupPut : public epics::pvAccess::ChannelPut,
|
||||
const epics::pvData::PVStructure::shared_pointer& pvReq);
|
||||
virtual ~PDBGroupPut();
|
||||
|
||||
virtual void destroy() { pvif.clear(); channel.reset(); requester.reset(); }
|
||||
virtual void lock() {}
|
||||
virtual void unlock() {}
|
||||
virtual std::tr1::shared_ptr<epics::pvAccess::Channel> getChannel() { return channel; }
|
||||
virtual void cancel() {}
|
||||
virtual void lastRequest() {}
|
||||
virtual void destroy() OVERRIDE FINAL { pvif.clear(); channel.reset(); requester.reset(); }
|
||||
virtual std::tr1::shared_ptr<epics::pvAccess::Channel> getChannel() OVERRIDE FINAL { return channel; }
|
||||
virtual void cancel() OVERRIDE FINAL {}
|
||||
virtual void lastRequest() OVERRIDE FINAL {}
|
||||
virtual void put(
|
||||
epics::pvData::PVStructure::shared_pointer const & pvPutStructure,
|
||||
epics::pvData::BitSet::shared_pointer const & putBitSet);
|
||||
virtual void get();
|
||||
epics::pvData::BitSet::shared_pointer const & putBitSet) OVERRIDE FINAL;
|
||||
virtual void get() OVERRIDE FINAL;
|
||||
};
|
||||
|
||||
struct PDBGroupMonitor : public BaseMonitor
|
||||
@@ -207,11 +208,11 @@ struct PDBGroupMonitor : public BaseMonitor
|
||||
const epics::pvData::PVStructure::shared_pointer& pvReq);
|
||||
virtual ~PDBGroupMonitor();
|
||||
|
||||
virtual void onStart();
|
||||
virtual void onStop();
|
||||
virtual void requestUpdate();
|
||||
virtual void onStart() OVERRIDE FINAL;
|
||||
virtual void onStop() OVERRIDE FINAL;
|
||||
virtual void requestUpdate() OVERRIDE FINAL;
|
||||
|
||||
virtual void destroy();
|
||||
virtual void destroy() OVERRIDE FINAL;
|
||||
|
||||
};
|
||||
|
||||
|
||||
+69
-57
@@ -3,12 +3,18 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <dbAccess.h>
|
||||
#include <epicsAtomic.h>
|
||||
#include <dbChannel.h>
|
||||
#include <dbStaticLib.h>
|
||||
#include <errlog.h>
|
||||
#include <dbNotify.h>
|
||||
#include <osiSock.h>
|
||||
#include <epicsAtomic.h>
|
||||
|
||||
#include <pv/epicsException.h>
|
||||
#include <pv/pvAccess.h>
|
||||
#include <pv/security.h>
|
||||
#include <pv/configuration.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include "helper.h"
|
||||
#include "pdbsingle.h"
|
||||
#include "pdb.h"
|
||||
@@ -30,42 +36,31 @@ void pdb_single_event(void *user_arg, struct dbChannel *chan,
|
||||
DBEvent *evt=(DBEvent*)user_arg;
|
||||
try{
|
||||
PDBSinglePV::shared_pointer self(std::tr1::static_pointer_cast<PDBSinglePV>(((PDBSinglePV*)evt->self)->shared_from_this()));
|
||||
PDBSinglePV::interested_remove_t temp;
|
||||
{
|
||||
bool doPost;
|
||||
{
|
||||
Guard G(self->lock);
|
||||
|
||||
if(evt->dbe_mask&DBE_PROPERTY)
|
||||
self->hadevent_PROPERTY = true;
|
||||
else
|
||||
self->hadevent_VALUE = true;
|
||||
|
||||
doPost = self->hadevent_VALUE && self->hadevent_PROPERTY;
|
||||
|
||||
if(doPost)
|
||||
self->interested_iterating = true;
|
||||
}
|
||||
Guard G(self->lock);
|
||||
|
||||
// we have exclusive use of self->scratch
|
||||
self->scratch.clear();
|
||||
{
|
||||
DBScanLocker L(dbChannelRecord(self->chan));
|
||||
// dbGet() into self->complete
|
||||
self->pvif->put(self->scratch, evt->dbe_mask, pfl);
|
||||
}
|
||||
|
||||
if(!doPost)
|
||||
return;
|
||||
if(evt->dbe_mask&DBE_PROPERTY)
|
||||
self->hadevent_PROPERTY = true;
|
||||
else
|
||||
self->hadevent_VALUE = true;
|
||||
|
||||
FOREACH(PDBSinglePV::interested_t::const_iterator, it, end, self->interested) {
|
||||
PDBSingleMonitor& mon = **it;
|
||||
mon.post(self->scratch);
|
||||
}
|
||||
if(self->hadevent_VALUE && self->hadevent_PROPERTY) {
|
||||
self->interested_iterating = true;
|
||||
|
||||
PDBSinglePV::interested_remove_t temp;
|
||||
{
|
||||
Guard G(self->lock);
|
||||
|
||||
assert(self->interested_iterating);
|
||||
FOREACH(PDBSinglePV::interested_t::const_iterator, it, end, self->interested) {
|
||||
PDBSingleMonitor& mon = **it;
|
||||
// from self->complete into monitor queue element
|
||||
mon.post(G, self->scratch); // G unlocked during call
|
||||
}
|
||||
|
||||
while(!self->interested_add.empty()) {
|
||||
PDBSinglePV::interested_t::iterator first(self->interested_add.begin());
|
||||
@@ -108,6 +103,10 @@ PDBSinglePV::PDBSinglePV(DBCH& chan,
|
||||
,hadevent_VALUE(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);
|
||||
fielddesc = std::tr1::static_pointer_cast<const pvd::Structure>(builder->dtype(this->chan));
|
||||
|
||||
@@ -125,8 +124,9 @@ PDBSinglePV::~PDBSinglePV()
|
||||
|
||||
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_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
|
||||
@@ -134,38 +134,38 @@ PDBSinglePV::connect(const std::tr1::shared_ptr<PDBProvider>& prov,
|
||||
const pva::ChannelRequester::shared_pointer& req)
|
||||
{
|
||||
PDBSingleChannel::shared_pointer ret(new PDBSingleChannel(shared_from_this(), req));
|
||||
|
||||
ret->cred.update(req);
|
||||
|
||||
ret->aspvt.add(chan, ret->cred);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void PDBSinglePV::addMonitor(PDBSingleMonitor* mon)
|
||||
{
|
||||
bool needpost = false;
|
||||
{
|
||||
Guard G(lock);
|
||||
if(interested.empty() && interested_add.empty()) {
|
||||
// first monitor
|
||||
// start subscription
|
||||
Guard G(lock);
|
||||
if(interested.empty() && interested_add.empty()) {
|
||||
// first monitor
|
||||
// start subscription
|
||||
|
||||
hadevent_VALUE = false;
|
||||
hadevent_PROPERTY = false;
|
||||
db_event_enable(evt_VALUE.subscript);
|
||||
db_event_enable(evt_PROPERTY.subscript);
|
||||
db_post_single_event(evt_VALUE.subscript);
|
||||
db_post_single_event(evt_PROPERTY.subscript);
|
||||
hadevent_VALUE = false;
|
||||
hadevent_PROPERTY = false;
|
||||
db_event_enable(evt_VALUE.subscript);
|
||||
db_event_enable(evt_PROPERTY.subscript);
|
||||
db_post_single_event(evt_VALUE.subscript);
|
||||
db_post_single_event(evt_PROPERTY.subscript);
|
||||
|
||||
} if(hadevent_VALUE && hadevent_PROPERTY) {
|
||||
// new subscriber and already had initial update
|
||||
needpost = true;
|
||||
} // else new subscriber, but no initial update. so just wait
|
||||
} else if(hadevent_VALUE && hadevent_PROPERTY) {
|
||||
// new subscriber and already had initial update
|
||||
mon->post(G);
|
||||
} // else new subscriber, but no initial update. so just wait
|
||||
|
||||
if(interested_iterating) {
|
||||
interested_add.insert(mon);
|
||||
} else {
|
||||
interested.insert(mon);
|
||||
}
|
||||
if(interested_iterating) {
|
||||
interested_add.insert(mon);
|
||||
} else {
|
||||
interested.insert(mon);
|
||||
}
|
||||
if(needpost)
|
||||
mon->post();
|
||||
}
|
||||
|
||||
void PDBSinglePV::removeMonitor(PDBSingleMonitor* mon)
|
||||
@@ -211,7 +211,15 @@ PDBSingleChannel::~PDBSingleChannel()
|
||||
|
||||
void PDBSingleChannel::printInfo(std::ostream& out)
|
||||
{
|
||||
out<<"PDBSingleChannel";
|
||||
if(aspvt.canWrite())
|
||||
out << "RW ";
|
||||
else
|
||||
out << "RO ";
|
||||
out<<(&cred.user[0])<<'@'<<(&cred.host[0]);
|
||||
for(size_t i=0, N=cred.groups.size(); i<N; i++) {
|
||||
out<<", "<<(&cred.groups[i][0]);
|
||||
}
|
||||
out<<"\n";
|
||||
}
|
||||
|
||||
pva::ChannelPut::shared_pointer
|
||||
@@ -233,7 +241,8 @@ PDBSingleChannel::createMonitor(
|
||||
PDBSingleMonitor::shared_pointer ret(new PDBSingleMonitor(pv->shared_from_this(), requester, pvRequest));
|
||||
ret->weakself = ret;
|
||||
assert(!!pv->complete);
|
||||
ret->connect(pv->complete);
|
||||
guard_t G(pv->lock);
|
||||
ret->connect(G, pv->complete);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -347,7 +356,10 @@ void PDBSinglePut::put(pvd::PVStructure::shared_pointer const & value,
|
||||
dbFldDes *fld = dbChannelFldDes(chan);
|
||||
|
||||
pvd::Status ret;
|
||||
if(dbChannelFieldType(chan)>=DBF_INLINK && dbChannelFieldType(chan)<=DBF_FWDLINK) {
|
||||
if(!channel->aspvt.canWrite()) {
|
||||
ret = pvd::Status::error("Put not permitted");
|
||||
|
||||
} else if(dbChannelFieldType(chan)>=DBF_INLINK && dbChannelFieldType(chan)<=DBF_FWDLINK) {
|
||||
try{
|
||||
std::string lval(value->getSubFieldT<pvd::PVScalar>("value")->getAs<std::string>());
|
||||
long status = dbChannelPutField(chan, DBF_STRING, lval.c_str(), 1);
|
||||
@@ -388,7 +400,7 @@ void PDBSinglePut::put(pvd::PVStructure::shared_pointer const & value,
|
||||
p2p::auto_ptr<PVIF> putpvif(channel->pv->builder->attach(channel->pv->chan, value, FieldName()));
|
||||
try{
|
||||
DBScanLocker L(chan);
|
||||
putpvif->get(*changed, doProc);
|
||||
ret = putpvif->get(*changed, doProc);
|
||||
|
||||
}catch(std::runtime_error& e){
|
||||
ret = pvd::Status::error(e.what());
|
||||
@@ -428,7 +440,7 @@ void PDBSinglePut::get()
|
||||
PDBSingleMonitor::PDBSingleMonitor(const PDBSinglePV::shared_pointer& pv,
|
||||
const requester_t::shared_pointer& requester,
|
||||
const pvd::PVStructure::shared_pointer& pvReq)
|
||||
:BaseMonitor(requester, pvReq)
|
||||
:BaseMonitor(pv->lock, requester, pvReq)
|
||||
,pv(pv)
|
||||
{
|
||||
epics::atomic::increment(num_instances);
|
||||
@@ -460,5 +472,5 @@ void PDBSingleMonitor::onStop()
|
||||
void PDBSingleMonitor::requestUpdate()
|
||||
{
|
||||
guard_t G(pv->lock);
|
||||
post();
|
||||
post(G);
|
||||
}
|
||||
|
||||
+19
-16
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <dbAccess.h>
|
||||
#include <dbNotify.h>
|
||||
#include <asLib.h>
|
||||
|
||||
#include <dbEvent.h>
|
||||
|
||||
@@ -15,11 +16,9 @@
|
||||
#include "pvif.h"
|
||||
#include "pdb.h"
|
||||
|
||||
#include <shareLib.h>
|
||||
|
||||
struct PDBSingleMonitor;
|
||||
|
||||
struct epicsShareClass PDBSinglePV : public PDBPV
|
||||
struct QSRV_API PDBSinglePV : public PDBPV
|
||||
{
|
||||
POINTER_DEFINITIONS(PDBSinglePV);
|
||||
weak_pointer weakself;
|
||||
@@ -31,6 +30,8 @@ struct epicsShareClass PDBSinglePV : public PDBPV
|
||||
* is locked.
|
||||
*/
|
||||
DBCH chan;
|
||||
// used for DBE_PROPERTY subscription when chan has filters
|
||||
DBCH chan2;
|
||||
PDBProvider::shared_pointer provider;
|
||||
|
||||
// only for use in pdb_single_event()
|
||||
@@ -62,9 +63,10 @@ struct epicsShareClass PDBSinglePV : public PDBPV
|
||||
|
||||
void activate();
|
||||
|
||||
virtual
|
||||
epics::pvAccess::Channel::shared_pointer
|
||||
connect(const std::tr1::shared_ptr<PDBProvider>& prov,
|
||||
const epics::pvAccess::ChannelRequester::shared_pointer& req);
|
||||
const epics::pvAccess::ChannelRequester::shared_pointer& req) OVERRIDE FINAL;
|
||||
|
||||
void addMonitor(PDBSingleMonitor*);
|
||||
void removeMonitor(PDBSingleMonitor*);
|
||||
@@ -77,6 +79,9 @@ struct PDBSingleChannel : public BaseChannel,
|
||||
POINTER_DEFINITIONS(PDBSingleChannel);
|
||||
|
||||
PDBSinglePV::shared_pointer pv;
|
||||
// storage referenced from aspvt
|
||||
ASCred cred;
|
||||
ASCLIENT aspvt;
|
||||
|
||||
static size_t num_instances;
|
||||
|
||||
@@ -120,16 +125,14 @@ struct PDBSinglePut : public epics::pvAccess::ChannelPut,
|
||||
const epics::pvData::PVStructure::shared_pointer& pvReq);
|
||||
virtual ~PDBSinglePut();
|
||||
|
||||
virtual void destroy() { pvif.reset(); channel.reset(); requester.reset(); }
|
||||
virtual void lock() {}
|
||||
virtual void unlock() {}
|
||||
virtual std::tr1::shared_ptr<epics::pvAccess::Channel> getChannel() { return channel; }
|
||||
virtual void cancel();
|
||||
virtual void lastRequest() {}
|
||||
virtual void destroy() OVERRIDE FINAL { pvif.reset(); channel.reset(); requester.reset(); }
|
||||
virtual std::tr1::shared_ptr<epics::pvAccess::Channel> getChannel() OVERRIDE FINAL { return channel; }
|
||||
virtual void cancel() OVERRIDE FINAL;
|
||||
virtual void lastRequest() OVERRIDE FINAL {}
|
||||
virtual void put(
|
||||
epics::pvData::PVStructure::shared_pointer const & pvPutStructure,
|
||||
epics::pvData::BitSet::shared_pointer const & putBitSet);
|
||||
virtual void get();
|
||||
epics::pvData::BitSet::shared_pointer const & putBitSet) OVERRIDE FINAL;
|
||||
virtual void get() OVERRIDE FINAL;
|
||||
};
|
||||
|
||||
struct PDBSingleMonitor : public BaseMonitor
|
||||
@@ -145,11 +148,11 @@ struct PDBSingleMonitor : public BaseMonitor
|
||||
const epics::pvData::PVStructure::shared_pointer& pvReq);
|
||||
virtual ~PDBSingleMonitor();
|
||||
|
||||
virtual void onStart();
|
||||
virtual void onStop();
|
||||
virtual void requestUpdate();
|
||||
virtual void onStart() OVERRIDE FINAL;
|
||||
virtual void onStop() OVERRIDE FINAL;
|
||||
virtual void requestUpdate() OVERRIDE FINAL;
|
||||
|
||||
virtual void destroy();
|
||||
virtual void destroy() OVERRIDE FINAL;
|
||||
};
|
||||
|
||||
#endif // PDBSINGLE_H
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
#ifndef PV_QSRV_H
|
||||
#define PV_QSRV_H
|
||||
|
||||
#include <epicsVersion.h>
|
||||
|
||||
#ifndef VERSION_INT
|
||||
# define VERSION_INT(V,R,M,P) ( ((V)<<24) | ((R)<<16) | ((M)<<8) | (P))
|
||||
#endif
|
||||
|
||||
/* generated header with EPICS_QSRV_*_VERSION macros */
|
||||
# include <pv/qsrvVersionNum.h>
|
||||
|
||||
#define QSRV_VERSION_INT VERSION_INT(EPICS_QSRV_MAJOR_VERSION, EPICS_QSRV_MINOR_VERSION, EPICS_QSRV_MAINTENANCE_VERSION, !(EPICS_QSRV_DEVELOPMENT_FLAG))
|
||||
|
||||
#define QSRV_ABI_VERSION_INT VERSION_INT(EPICS_QSRV_ABI_MAJOR_VERSION, EPICS_QSRV_ABI_MINOR_VERSION, 0, 0)
|
||||
|
||||
#if defined(QSRV_API_BUILDING) && defined(epicsExportSharedSymbols)
|
||||
# error Use QSRV_API or shareLib.h not both
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) || defined(__CYGWIN__)
|
||||
|
||||
# if defined(QSRV_API_BUILDING) && defined(EPICS_BUILD_DLL)
|
||||
/* building library as dll */
|
||||
# define QSRV_API __declspec(dllexport)
|
||||
# elif !defined(QSRV_API_BUILDING) && defined(EPICS_CALL_DLL)
|
||||
/* calling library in dll form */
|
||||
# define QSRV_API __declspec(dllimport)
|
||||
# endif
|
||||
|
||||
#elif __GNUC__ >= 4
|
||||
# define QSRV_API __attribute__ ((visibility("default")))
|
||||
#endif
|
||||
|
||||
#ifndef QSRV_API
|
||||
# define QSRV_API
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct link; /* aka. DBLINK from link.h */
|
||||
|
||||
/** returns QSRV_VERSION_INT captured at compilation time */
|
||||
QSRV_API unsigned qsrvVersion(void);
|
||||
|
||||
/** returns QSRV_ABI_VERSION_INT captured at compilation time */
|
||||
QSRV_API unsigned qsrvABIVersion(void);
|
||||
|
||||
QSRV_API void testqsrvWaitForLinkEvent(struct link *plink);
|
||||
|
||||
/** Call before testIocShutdownOk()
|
||||
@code
|
||||
testdbPrepare();
|
||||
...
|
||||
testIocInitOk();
|
||||
...
|
||||
testqsrvShutdownOk();
|
||||
testIocShutdownOk();
|
||||
testqsrvCleanup();
|
||||
testdbCleanup();
|
||||
@endcode
|
||||
*/
|
||||
QSRV_API void testqsrvShutdownOk(void);
|
||||
|
||||
/** Call after testIocShutdownOk() and before testdbCleanup()
|
||||
@code
|
||||
testdbPrepare();
|
||||
...
|
||||
testIocInitOk();
|
||||
...
|
||||
testqsrvShutdownOk();
|
||||
testIocShutdownOk();
|
||||
testqsrvCleanup();
|
||||
testdbCleanup();
|
||||
@endcode
|
||||
*/
|
||||
QSRV_API void testqsrvCleanup(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PV_QSRV_H */
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef PV_QSRV_H
|
||||
# error qsrvVersionNum.h should never be included directly. Include <pv/qsrv.h>
|
||||
#endif
|
||||
#define EPICS_QSRV_MAJOR_VERSION @EPICS_QSRV_MAJOR_VERSION@
|
||||
#define EPICS_QSRV_MINOR_VERSION @EPICS_QSRV_MINOR_VERSION@
|
||||
#define EPICS_QSRV_MAINTENANCE_VERSION @EPICS_QSRV_MAINTENANCE_VERSION@
|
||||
#define EPICS_QSRV_DEVELOPMENT_FLAG @EPICS_QSRV_DEVELOPMENT_FLAG@
|
||||
#define EPICS_QSRV_ABI_MAJOR_VERSION @EPICS_QSRV_ABI_MAJOR_VERSION@
|
||||
#define EPICS_QSRV_ABI_MINOR_VERSION @EPICS_QSRV_ABI_MINOR_VERSION@
|
||||
+260
-753
File diff suppressed because it is too large
Load Diff
+159
-247
@@ -10,25 +10,53 @@
|
||||
#include <dbCommon.h>
|
||||
#include <dbLink.h>
|
||||
#include <dbScan.h>
|
||||
#include <epicsExport.h>
|
||||
#include <errlog.h>
|
||||
#include <initHooks.h>
|
||||
#include <alarm.h>
|
||||
#include <epicsExit.h>
|
||||
#include <epicsAtomic.h>
|
||||
#include <epicsThreadPool.h>
|
||||
#include <link.h>
|
||||
#include <dbJLink.h>
|
||||
#include <errlog.h>
|
||||
#include <epicsThread.h>
|
||||
#include <epicsMutex.h>
|
||||
#include <epicsEvent.h>
|
||||
#include <dbChannel.h>
|
||||
#include <dbStaticLib.h>
|
||||
#include <dbLock.h>
|
||||
#include <dbEvent.h>
|
||||
#include <epicsVersion.h>
|
||||
|
||||
#include <pv/pvAccess.h>
|
||||
#include <pv/clientFactory.h>
|
||||
#include <pv/status.h>
|
||||
#include <pv/bitSet.h>
|
||||
#include <pv/pvData.h>
|
||||
|
||||
#include <pva/client.h>
|
||||
#include <pv/anyscalar.h>
|
||||
#include <pv/thread.h>
|
||||
#include <pv/lock.h>
|
||||
#include <pv/iocshelper.h>
|
||||
|
||||
#include <pv/sharedPtr.h>
|
||||
|
||||
#include "helper.h"
|
||||
#include "iocshelper.h"
|
||||
#include "pvif.h"
|
||||
#include "tpool.h"
|
||||
|
||||
extern int pvaLinkDebug;
|
||||
extern int pvaLinkIsolate;
|
||||
extern "C" {
|
||||
QSRV_API extern int pvaLinkDebug;
|
||||
QSRV_API extern int pvaLinkIsolate;
|
||||
QSRV_API extern int pvaLinkNWorkers;
|
||||
}
|
||||
|
||||
#if 0
|
||||
# define TRACE(X) std::cerr<<"PVAL "<<__func__<<" " X <<"\n"
|
||||
#else
|
||||
# define TRACE(X) do {} while(0)
|
||||
#endif
|
||||
|
||||
// pvaLink and pvaLinkChannel have ->debug
|
||||
#define DEBUG(OBJ, X) do{ if((OBJ)->debug) std::cout X<<"\n"; }while(0)
|
||||
|
||||
namespace pvalink {
|
||||
|
||||
@@ -41,289 +69,173 @@ typedef epicsGuardRelease<pvd::Mutex> UnGuard;
|
||||
struct pvaLink;
|
||||
struct pvaLinkChannel;
|
||||
|
||||
struct pvaGlobal_t {
|
||||
pva::ChannelProvider::shared_pointer provider;
|
||||
extern lset pva_lset;
|
||||
extern jlif lsetPVA;
|
||||
|
||||
pvd::StructureConstPtr reqtype;
|
||||
pvd::PVDataCreatePtr create;
|
||||
struct pvaLinkConfig : public jlink
|
||||
{
|
||||
// configuration, output of jlif parsing
|
||||
//! Channel (aka PV) name string
|
||||
std::string channelName;
|
||||
//! sub-field within addressed PVStructure
|
||||
std::string fieldName;
|
||||
|
||||
size_t queueSize;
|
||||
|
||||
enum pp_t {
|
||||
NPP,
|
||||
Default, // for put() only. For monitor, treated as NPP
|
||||
PP, // for put() only, For monitor, treated as NPP
|
||||
CP, // for monitor only, put treats as pp
|
||||
CPP, // for monitor only, put treats as pp
|
||||
} pp;
|
||||
enum ms_t {
|
||||
NMS,
|
||||
MS,
|
||||
MSI,
|
||||
} ms;
|
||||
|
||||
bool defer, pipeline, time, retry, local, always;
|
||||
int monorder;
|
||||
|
||||
// internals used by jlif parsing
|
||||
std::string jkey;
|
||||
|
||||
pvaLinkConfig();
|
||||
virtual ~pvaLinkConfig();
|
||||
};
|
||||
|
||||
struct pvaGlobal_t {
|
||||
pvac::ClientProvider provider_local,
|
||||
provider_remote;
|
||||
|
||||
const pvd::PVDataCreatePtr create;
|
||||
|
||||
WorkQueue queue;
|
||||
|
||||
pvd::Mutex lock;
|
||||
|
||||
struct Scan {
|
||||
// the PVA channel which triggered this scan
|
||||
std::tr1::weak_ptr<pvaLinkChannel> chan;
|
||||
bool usecached;
|
||||
Scan() :usecached(false) {}
|
||||
};
|
||||
bool running; // set after dbEvent is initialized and safe to use
|
||||
|
||||
epicsThreadPrivate<Scan> scanmagic;
|
||||
epicsThreadPool *scanpool;
|
||||
|
||||
typedef std::map<std::string, std::tr1::shared_ptr<pvaLinkChannel> > channels_t;
|
||||
// a tuple of channel name and printed pvRequest (or Monitor)
|
||||
typedef std::pair<std::string, std::string> channels_key_t;
|
||||
// pvaLinkChannel dtor prunes dead entires
|
||||
typedef std::map<channels_key_t, std::tr1::weak_ptr<pvaLinkChannel> > channels_t;
|
||||
// Cache of active Channels (really about caching Monitor)
|
||||
channels_t channels;
|
||||
|
||||
std::tr1::shared_ptr<pvaLinkChannel> connect(const char *name);
|
||||
|
||||
pvaGlobal_t();
|
||||
~pvaGlobal_t()
|
||||
{
|
||||
provider->destroy();
|
||||
epicsThreadPoolDestroy(scanpool);
|
||||
}
|
||||
~pvaGlobal_t();
|
||||
};
|
||||
extern pvaGlobal_t *pvaGlobal;
|
||||
|
||||
struct pvaLinkChannel : public pva::ChannelRequester, pva::MonitorRequester,
|
||||
std::tr1::enable_shared_from_this<pvaLinkChannel>
|
||||
struct pvaLinkChannel : public pvac::ClientChannel::MonitorCallback,
|
||||
public pvac::ClientChannel::PutCallback,
|
||||
public epicsThreadRunable,
|
||||
public std::tr1::enable_shared_from_this<pvaLinkChannel>
|
||||
{
|
||||
const std::string name;
|
||||
const pvaGlobal_t::channels_key_t key; // tuple of (channelName, pvRequest key)
|
||||
const pvd::PVStructure::const_shared_pointer pvRequest; // used with monitor
|
||||
|
||||
static size_t refs;
|
||||
|
||||
typedef std::set<pvaLink*> links_t;
|
||||
links_t links;
|
||||
static size_t num_instances;
|
||||
|
||||
pvd::Mutex lock;
|
||||
epicsEvent run_done; // used by testing code
|
||||
|
||||
pva::Channel::shared_pointer chan;
|
||||
pvac::ClientChannel chan;
|
||||
pvac::Monitor op_mon;
|
||||
pvac::Operation op_put;
|
||||
|
||||
pva::Monitor::shared_pointer chanmon;
|
||||
//pva::ChannelPut::shared_pointer chanput;
|
||||
std::string providerName;
|
||||
size_t num_disconnect, num_type_change;
|
||||
bool connected;
|
||||
bool connected_latched; // connection status at the run()
|
||||
bool isatomic;
|
||||
bool queued; // added to WorkQueue
|
||||
bool debug; // set if any jlink::debug is set
|
||||
std::tr1::shared_ptr<const void> previous_root;
|
||||
|
||||
pvd::PVStructurePtr lastval;
|
||||
pvd::PVScalarPtr isatomic;
|
||||
struct LinkSort {
|
||||
bool operator()(const pvaLink *L, const pvaLink *R) const;
|
||||
};
|
||||
|
||||
epicsJob *scanjob;
|
||||
std::tr1::shared_ptr<pvaLinkChannel> scanself; // create ref loop while scan is queued
|
||||
bool scanatomic;
|
||||
typedef std::set<pvaLink*, LinkSort> links_t;
|
||||
|
||||
pvaLinkChannel(const char *name)
|
||||
:name(name)
|
||||
,scanjob(epicsJobCreate(pvaGlobal->scanpool, &pvaLinkChannel::scan, this))
|
||||
,scanatomic(false)
|
||||
{
|
||||
if(!scanjob)
|
||||
throw std::runtime_error("failed to create job for pvaLink");
|
||||
epics::atomic::increment(refs);
|
||||
}
|
||||
virtual ~pvaLinkChannel() {
|
||||
Guard G(lock);
|
||||
assert(links.empty());
|
||||
epicsJobDestroy(scanjob);
|
||||
scanjob = NULL;
|
||||
epics::atomic::decrement(refs);
|
||||
std::cerr<<"pvaLinkChannel: destroy "<<name<<"\n";
|
||||
}
|
||||
// list of currently attached links. maintained by pvaLink ctor/dtor
|
||||
// TODO: sort by PHAS
|
||||
links_t links;
|
||||
|
||||
void doConnect() {
|
||||
// TODO: local PVA?
|
||||
Guard G(lock);
|
||||
chan = pvaGlobal->provider->createChannel(name, shared_from_this());
|
||||
channelStateChange(chan, chan->getConnectionState());
|
||||
}
|
||||
void doClose() {
|
||||
Guard G(lock);
|
||||
errlogPrintf("pvaLink closing %s\n", name.c_str());
|
||||
channelStateChange(chan, pva::Channel::DESTROYED);
|
||||
chan->destroy();
|
||||
chan.reset();
|
||||
std::cerr<<"pvaLink: channel destroy "<<name<<"\n";
|
||||
}
|
||||
// set when 'links' is modified to trigger re-compute of record scan list
|
||||
bool links_changed;
|
||||
|
||||
void triggerProc(bool atomic=false, bool force=false);
|
||||
pvaLinkChannel(const pvaGlobal_t::channels_key_t& key, const epics::pvData::PVStructure::const_shared_pointer &pvRequest);
|
||||
virtual ~pvaLinkChannel();
|
||||
|
||||
static void scan(void* arg, epicsJobMode mode);
|
||||
void open();
|
||||
void put(bool force=false); // begin Put op.
|
||||
|
||||
virtual std::string getRequesterName() { return "pvaLink"; }
|
||||
virtual void message(std::string const & message, pva::MessageType messageType)
|
||||
{
|
||||
errlogPrintf("%s pvaLink \"%s\": %s\n",
|
||||
pvd::getMessageTypeName(messageType).c_str(),
|
||||
name.c_str(),
|
||||
message.c_str());
|
||||
}
|
||||
// pvac::ClientChanel::MonitorCallback
|
||||
virtual void monitorEvent(const pvac::MonitorEvent& evt) OVERRIDE FINAL;
|
||||
|
||||
virtual void channelCreated(const epics::pvData::Status& status, pva::Channel::shared_pointer const & channel)
|
||||
{
|
||||
if(!status.isSuccess()) {
|
||||
errlogPrintf("pvaLink create fails %s: %s\n", name.c_str(), status.getMessage().c_str());
|
||||
return;
|
||||
}
|
||||
Guard G(lock);
|
||||
//assert(chan==channel); // may be called before createChannel() returns
|
||||
chan = channel;
|
||||
}
|
||||
// pvac::ClientChanel::PutCallback
|
||||
virtual void putBuild(const epics::pvData::StructureConstPtr& build, pvac::ClientChannel::PutCallback::Args& args) OVERRIDE FINAL;
|
||||
virtual void putDone(const pvac::PutEvent& evt) OVERRIDE FINAL;
|
||||
private:
|
||||
virtual void run() OVERRIDE FINAL;
|
||||
void run_dbProcess(size_t idx); // idx is index in scan_records
|
||||
|
||||
virtual void channelStateChange(pva::Channel::shared_pointer const & channel, pva::Channel::ConnectionState connectionState);
|
||||
// ==== Treat remaining as local to run()
|
||||
|
||||
virtual void monitorConnect(pvd::Status const & status,
|
||||
pva::Monitor::shared_pointer const & monitor,
|
||||
pvd::StructureConstPtr const & structure);
|
||||
|
||||
virtual void monitorEvent(pva::Monitor::shared_pointer const & monitor);
|
||||
|
||||
virtual void unlisten(pva::Monitor::shared_pointer const & monitor)
|
||||
{
|
||||
// what to do??
|
||||
}
|
||||
std::vector<dbCommon*> scan_records;
|
||||
std::vector<bool> scan_check_passive;
|
||||
std::vector<epics::pvData::BitSet> scan_changed;
|
||||
|
||||
DBManyLock atomic_lock;
|
||||
};
|
||||
|
||||
struct pvaLink : public jlink
|
||||
struct pvaLink : public pvaLinkConfig
|
||||
{
|
||||
static size_t refs;
|
||||
static size_t num_instances;
|
||||
|
||||
bool alive; // attempt to catch some use after free
|
||||
|
||||
DBLINK * plink; // may be NULL
|
||||
unsigned linkmods;
|
||||
unsigned parse_level;
|
||||
|
||||
std::string name, field;
|
||||
const pva::Channel::shared_pointer chan;
|
||||
bool alive; // attempt to catch some use after free
|
||||
|
||||
std::tr1::shared_ptr<pvaLinkChannel> lchan;
|
||||
|
||||
pvd::PVScalarPtr valueS;
|
||||
pvd::PVScalarArray::shared_pointer valueA;
|
||||
pvd::PVScalar::shared_pointer sevr, sec, nsec;
|
||||
pvd::ScalarType etype;
|
||||
bool used_scratch, used_queue;
|
||||
pvd::shared_vector<const void> put_scratch, put_queue;
|
||||
|
||||
struct Value {
|
||||
bool valid;
|
||||
bool scalar;
|
||||
pvd::ScalarType etype;
|
||||
pvd::shared_vector<const void> valueA;
|
||||
dbrbuf valueS;
|
||||
epicsUInt16 sevr;
|
||||
epicsTimeStamp time;
|
||||
Value() :valid(false) {}
|
||||
void clear() {
|
||||
valid = false;
|
||||
valueA.clear();
|
||||
}
|
||||
};
|
||||
// cached fields from channel op_mon
|
||||
// updated in onTypeChange()
|
||||
epics::pvData::PVField::const_shared_pointer fld_value;
|
||||
epics::pvData::PVScalar::const_shared_pointer fld_severity,
|
||||
fld_seconds,
|
||||
fld_nanoseconds;
|
||||
epics::pvData::PVStructure::const_shared_pointer fld_display,
|
||||
fld_control,
|
||||
fld_valueAlarm;
|
||||
epics::pvData::BitSet proc_changed;
|
||||
|
||||
Value atomcache;
|
||||
// cached snapshot of alarm and timestamp
|
||||
// captured in pvaGetValue().
|
||||
// we choose not to ensure consistency with display/control meta-data
|
||||
epicsTimeStamp snap_time;
|
||||
short snap_severity;
|
||||
|
||||
pvaLink()
|
||||
:plink(0)
|
||||
,linkmods(0)
|
||||
,parse_level(0)
|
||||
,alive(true)
|
||||
{
|
||||
epics::atomic::increment(refs);
|
||||
//TODO: valgrind tells me these aren't initialized by Base, but probably should be.
|
||||
parseDepth = 0;
|
||||
parent = 0;
|
||||
}
|
||||
pvaLink();
|
||||
virtual ~pvaLink();
|
||||
|
||||
void open()
|
||||
{
|
||||
if(this->name.empty())
|
||||
throw std::logic_error("open() w/o target PV name");
|
||||
this->name = name;
|
||||
//TODO: how to distinguish "record.FLD" from pva "channel.subfield"?
|
||||
size_t dot = this->name.find_first_of('.');
|
||||
if(dot!=this->name.npos) {
|
||||
field = this->name.substr(dot+1);
|
||||
this->name = this->name.substr(0, dot);
|
||||
}
|
||||
lchan = pvaGlobal->connect(this->name.c_str());
|
||||
Guard G(lchan->lock);
|
||||
lchan->links.insert(this);
|
||||
if(lchan->lastval)
|
||||
attach();
|
||||
}
|
||||
~pvaLink()
|
||||
{
|
||||
alive = false;
|
||||
if(lchan) { // may be NULL if parsing fails
|
||||
Guard G(lchan->lock);
|
||||
detach();
|
||||
lchan->links.erase(this);
|
||||
if(lchan->links.empty()) {
|
||||
pvaGlobal->channels.erase(lchan->name);
|
||||
lchan->doClose();
|
||||
}
|
||||
}
|
||||
epics::atomic::decrement(refs);
|
||||
}
|
||||
// returns pvRequest to be used with monitor
|
||||
pvd::PVStructurePtr makeRequest();
|
||||
|
||||
void detach()
|
||||
{
|
||||
valueS.reset();
|
||||
valueA.reset();
|
||||
sevr.reset();
|
||||
sec.reset();
|
||||
nsec.reset();
|
||||
}
|
||||
bool valid() const;
|
||||
|
||||
bool attach()
|
||||
{
|
||||
pvd::PVStructurePtr base(lchan->lastval);
|
||||
// fetch a sub-sub-field of the top monitored field.
|
||||
pvd::PVField::const_shared_pointer getSubField(const char *name);
|
||||
|
||||
if(!field.empty())
|
||||
base = base->getSubField<pvd::PVStructure>(field);
|
||||
if(!base) {
|
||||
errlogPrintf("pvaLink not %s%c%s\n", name.c_str(), field.empty() ? ' ' : '.', field.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
pvd::PVFieldPtr value(base->getSubField("value"));
|
||||
switch(value->getField()->getType())
|
||||
{
|
||||
case pvd::scalar:
|
||||
valueS = std::tr1::static_pointer_cast<pvd::PVScalar>(value);
|
||||
etype = valueS->getScalar()->getScalarType();
|
||||
break;
|
||||
case pvd::scalarArray:
|
||||
valueA = std::tr1::static_pointer_cast<pvd::PVScalarArray>(value);
|
||||
etype = valueA->getScalarArray()->getElementType();
|
||||
break;
|
||||
default:
|
||||
errlogPrintf("pvaLink not .value : %s%c%s\n", name.c_str(), field.empty() ? ' ' : '.', field.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
sevr = base->getSubField<pvd::PVScalar>("alarm.severity");
|
||||
sec = base->getSubField<pvd::PVScalar>("timeStamp.secondsPastEpoch");
|
||||
nsec = base->getSubField<pvd::PVScalar>("timeStamp.nanoseconds");
|
||||
return true;
|
||||
}
|
||||
|
||||
void get(Value& v)
|
||||
{
|
||||
if(valueA) {
|
||||
valueA->getAs<const void>(v.valueA);
|
||||
v.etype = v.valueA.original_type();
|
||||
v.scalar = false;
|
||||
|
||||
} else if(valueS) {
|
||||
|
||||
switch(etype) {
|
||||
#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case pvd::pv ## PVACODE: v.valueS.dbf_##DBFTYPE = valueS->getAs<PVATYPE>(); break;
|
||||
#define CASE_SQUEEZE_INT64
|
||||
#include "pvatypemap.h"
|
||||
#undef CASE_SQUEEZE_INT64
|
||||
#undef CASE
|
||||
case pvd::pvString: {
|
||||
strncpy(v.valueS.dbf_STRING, valueS->getAs<std::string>().c_str(), sizeof(v.valueS.dbf_STRING));
|
||||
v.valueS.dbf_STRING[sizeof(v.valueS.dbf_STRING)-1] = '\0';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("putValue unsupported DBR code");
|
||||
}
|
||||
|
||||
v.etype = etype;
|
||||
v.scalar = true;
|
||||
}
|
||||
|
||||
v.sevr = sevr->getAs<epicsUInt16>();
|
||||
v.time.secPastEpoch = sec->getAs<epicsUInt32>()-POSIX_TIME_AT_EPICS_EPOCH;
|
||||
v.time.nsec = nsec->getAs<epicsUInt32>();
|
||||
v.valid = true;
|
||||
}
|
||||
void onDisconnect();
|
||||
void onTypeChange();
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,382 @@
|
||||
|
||||
#include <alarm.h>
|
||||
|
||||
#include <pv/reftrack.h>
|
||||
|
||||
#include "pvalink.h"
|
||||
|
||||
int pvaLinkNWorkers = 1;
|
||||
|
||||
namespace pvalink {
|
||||
|
||||
pvaGlobal_t *pvaGlobal;
|
||||
|
||||
|
||||
pvaGlobal_t::pvaGlobal_t()
|
||||
:create(pvd::getPVDataCreate())
|
||||
,queue("PVAL")
|
||||
,running(false)
|
||||
{
|
||||
// worker should be above PVA worker priority?
|
||||
queue.start(std::max(1, pvaLinkNWorkers), epicsThreadPriorityMedium);
|
||||
}
|
||||
|
||||
pvaGlobal_t::~pvaGlobal_t()
|
||||
{
|
||||
}
|
||||
|
||||
size_t pvaLinkChannel::num_instances;
|
||||
size_t pvaLink::num_instances;
|
||||
|
||||
|
||||
bool pvaLinkChannel::LinkSort::operator()(const pvaLink *L, const pvaLink *R) const {
|
||||
if(L->monorder==R->monorder)
|
||||
return L < R;
|
||||
return L->monorder < R->monorder;
|
||||
}
|
||||
|
||||
// being called with pvaGlobal::lock held
|
||||
pvaLinkChannel::pvaLinkChannel(const pvaGlobal_t::channels_key_t &key, const pvd::PVStructure::const_shared_pointer& pvRequest)
|
||||
:key(key)
|
||||
,pvRequest(pvRequest)
|
||||
,num_disconnect(0u)
|
||||
,num_type_change(0u)
|
||||
,connected(false)
|
||||
,connected_latched(false)
|
||||
,isatomic(false)
|
||||
,queued(false)
|
||||
,debug(false)
|
||||
,links_changed(false)
|
||||
{}
|
||||
|
||||
pvaLinkChannel::~pvaLinkChannel() {
|
||||
{
|
||||
Guard G(pvaGlobal->lock);
|
||||
pvaGlobal->channels.erase(key);
|
||||
}
|
||||
|
||||
Guard G(lock);
|
||||
|
||||
assert(links.empty());
|
||||
REFTRACE_DECREMENT(num_instances);
|
||||
}
|
||||
|
||||
void pvaLinkChannel::open()
|
||||
{
|
||||
Guard G(lock);
|
||||
|
||||
try {
|
||||
chan = pvaGlobal->provider_local.connect(key.first);
|
||||
DEBUG(this, <<key.first<<" OPEN Local");
|
||||
providerName = pvaGlobal->provider_local.name();
|
||||
} catch(std::exception& e){
|
||||
// The PDBProvider doesn't have a way to communicate to us
|
||||
// whether this is an invalid record or group name,
|
||||
// or if this is some sort of internal error.
|
||||
// So we are forced to assume it is an invalid name.
|
||||
DEBUG(this, <<key.first<<" OPEN Not local "<<e.what());
|
||||
}
|
||||
if(!pvaLinkIsolate && !chan) {
|
||||
chan = pvaGlobal->provider_remote.connect(key.first);
|
||||
DEBUG(this, <<key.first<<" OPEN Remote ");
|
||||
providerName = pvaGlobal->provider_remote.name();
|
||||
}
|
||||
|
||||
op_mon = chan.monitor(this, pvRequest);
|
||||
|
||||
REFTRACE_INCREMENT(num_instances);
|
||||
}
|
||||
|
||||
static
|
||||
pvd::StructureConstPtr putRequestType = pvd::getFieldCreate()->createFieldBuilder()
|
||||
->addNestedStructure("field")
|
||||
->endNested()
|
||||
->addNestedStructure("record")
|
||||
->addNestedStructure("_options")
|
||||
->add("block", pvd::pvBoolean)
|
||||
->add("process", pvd::pvString) // "true", "false", or "passive"
|
||||
->endNested()
|
||||
->endNested()
|
||||
->createStructure();
|
||||
|
||||
// call with channel lock held
|
||||
void pvaLinkChannel::put(bool force)
|
||||
{
|
||||
pvd::PVStructurePtr pvReq(pvd::getPVDataCreate()->createPVStructure(putRequestType));
|
||||
pvReq->getSubFieldT<pvd::PVBoolean>("record._options.block")->put(false); // TODO: some way to expose completion...
|
||||
|
||||
unsigned reqProcess = 0;
|
||||
bool doit = force;
|
||||
for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it)
|
||||
{
|
||||
pvaLink *link = *it;
|
||||
|
||||
if(!link->used_scratch) continue;
|
||||
|
||||
pvd::shared_vector<const void> temp;
|
||||
temp.swap(link->put_scratch);
|
||||
link->used_scratch = false;
|
||||
temp.swap(link->put_queue);
|
||||
link->used_queue = true;
|
||||
|
||||
doit = true;
|
||||
|
||||
switch(link->pp) {
|
||||
case pvaLink::NPP:
|
||||
reqProcess |= 1;
|
||||
break;
|
||||
case pvaLink::Default:
|
||||
break;
|
||||
case pvaLink::PP:
|
||||
case pvaLink::CP:
|
||||
case pvaLink::CPP:
|
||||
reqProcess |= 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* By default, use remote default (passive).
|
||||
* Request processing, or not, if any link asks.
|
||||
* Prefer PP over NPP if both are specified.
|
||||
*
|
||||
* TODO: per field granularity?
|
||||
*/
|
||||
const char *proc = "passive";
|
||||
if((reqProcess&2) || force) {
|
||||
proc = "true";
|
||||
} else if(reqProcess&1) {
|
||||
proc = "false";
|
||||
}
|
||||
pvReq->getSubFieldT<pvd::PVString>("record._options.process")->put(proc);
|
||||
|
||||
DEBUG(this, <<key.first<<"Start put "<<doit);
|
||||
if(doit) {
|
||||
// start net Put, cancels in-progress put
|
||||
op_put = chan.put(this, pvReq);
|
||||
}
|
||||
}
|
||||
|
||||
void pvaLinkChannel::putBuild(const epics::pvData::StructureConstPtr& build, pvac::ClientChannel::PutCallback::Args& args)
|
||||
{
|
||||
Guard G(lock);
|
||||
|
||||
pvd::PVStructurePtr top(pvaGlobal->create->createPVStructure(build));
|
||||
|
||||
for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it)
|
||||
{
|
||||
pvaLink *link = *it;
|
||||
|
||||
if(!link->used_queue) continue;
|
||||
link->used_queue = false; // clear early so unexpected exception won't get us in a retry loop
|
||||
|
||||
pvd::PVFieldPtr value(link->fieldName.empty() ? pvd::PVFieldPtr(top) : top->getSubField(link->fieldName));
|
||||
if(value && value->getField()->getType()==pvd::structure) {
|
||||
// maybe drill into NTScalar et al.
|
||||
pvd::PVFieldPtr sub(static_cast<pvd::PVStructure*>(value.get())->getSubField("value"));
|
||||
if(sub)
|
||||
value.swap(sub);
|
||||
}
|
||||
|
||||
if(!value) continue; // TODO: how to signal error?
|
||||
|
||||
pvd::PVStringArray::const_svector choices; // TODO populate from op_mon
|
||||
|
||||
DEBUG(this, <<key.first<<" <- "<<value->getFullName());
|
||||
copyDBF2PVD(link->put_queue, value, args.tosend, choices);
|
||||
|
||||
link->put_queue.clear();
|
||||
}
|
||||
DEBUG(this, <<key.first<<" Put built");
|
||||
|
||||
args.root = top;
|
||||
}
|
||||
|
||||
void pvaLinkChannel::putDone(const pvac::PutEvent& evt)
|
||||
{
|
||||
if(evt.event==pvac::PutEvent::Fail) {
|
||||
errlogPrintf("%s PVA link put ERROR: %s\n", key.first.c_str(), evt.message.c_str());
|
||||
}
|
||||
|
||||
Guard G(lock);
|
||||
|
||||
DEBUG(this, <<key.first<<" Put result "<<evt.event);
|
||||
|
||||
op_put = pvac::Operation();
|
||||
|
||||
if(evt.event==pvac::PutEvent::Success) {
|
||||
// see if we need start a queue'd put
|
||||
put();
|
||||
}
|
||||
}
|
||||
|
||||
void pvaLinkChannel::monitorEvent(const pvac::MonitorEvent& evt)
|
||||
{
|
||||
bool queue = false;
|
||||
|
||||
{
|
||||
DEBUG(this, <<key.first<<" EVENT "<<evt.event);
|
||||
Guard G(lock);
|
||||
|
||||
switch(evt.event) {
|
||||
case pvac::MonitorEvent::Disconnect:
|
||||
case pvac::MonitorEvent::Data:
|
||||
connected = evt.event == pvac::MonitorEvent::Data;
|
||||
queue = true;
|
||||
break;
|
||||
case pvac::MonitorEvent::Cancel:
|
||||
break; // no-op
|
||||
case pvac::MonitorEvent::Fail:
|
||||
connected = false;
|
||||
queue = true;
|
||||
errlogPrintf("%s: PVA link monitor ERROR: %s\n", chan.name().c_str(), evt.message.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
if(queued)
|
||||
return; // already scheduled
|
||||
|
||||
queued = queue;
|
||||
}
|
||||
|
||||
if(queue) {
|
||||
pvaGlobal->queue.add(shared_from_this());
|
||||
}
|
||||
}
|
||||
|
||||
// the work in calling dbProcess() which is common to
|
||||
// both dbScanLock() and dbScanLockMany()
|
||||
void pvaLinkChannel::run_dbProcess(size_t idx)
|
||||
{
|
||||
dbCommon *precord = scan_records[idx];
|
||||
|
||||
if(scan_check_passive[idx] && precord->scan!=0) {
|
||||
return;
|
||||
|
||||
} else if(connected_latched && !op_mon.changed.logical_and(scan_changed[idx])) {
|
||||
return;
|
||||
|
||||
} else if (precord->pact) {
|
||||
if (precord->tpro)
|
||||
printf("%s: Active %s\n",
|
||||
epicsThreadGetNameSelf(), precord->name);
|
||||
precord->rpro = TRUE;
|
||||
|
||||
}
|
||||
dbProcess(precord);
|
||||
}
|
||||
|
||||
// Running from global WorkQueue thread
|
||||
void pvaLinkChannel::run()
|
||||
{
|
||||
bool requeue = false;
|
||||
{
|
||||
Guard G(lock);
|
||||
|
||||
queued = false;
|
||||
|
||||
connected_latched = connected;
|
||||
|
||||
// pop next update from monitor queue.
|
||||
// still under lock to safeguard concurrent calls to lset functions
|
||||
if(connected && !op_mon.poll()) {
|
||||
DEBUG(this, <<key.first<<" RUN "<<"empty");
|
||||
run_done.signal();
|
||||
return; // monitor queue is empty, nothing more to do here
|
||||
}
|
||||
|
||||
DEBUG(this, <<key.first<<" RUN "<<(connected_latched?"connected":"disconnected"));
|
||||
|
||||
assert(!connected || !!op_mon.root);
|
||||
|
||||
if(!connected) {
|
||||
num_disconnect++;
|
||||
|
||||
// cancel pending put operations
|
||||
op_put = pvac::Operation();
|
||||
|
||||
for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it)
|
||||
{
|
||||
pvaLink *link = *it;
|
||||
link->onDisconnect();
|
||||
}
|
||||
|
||||
// Don't clear previous_root on disconnect.
|
||||
// We will usually re-connect with the same type,
|
||||
// and may get back the same PVStructure.
|
||||
|
||||
} else if(previous_root.get() != (const void*)op_mon.root.get()) {
|
||||
num_type_change++;
|
||||
|
||||
for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it)
|
||||
{
|
||||
pvaLink *link = *it;
|
||||
link->onTypeChange();
|
||||
}
|
||||
|
||||
previous_root = std::tr1::static_pointer_cast<const void>(op_mon.root);
|
||||
}
|
||||
|
||||
// at this point we know we will re-queue, but not immediately
|
||||
// so an expected error won't get us stuck in a tight loop.
|
||||
requeue = queued = connected_latched;
|
||||
|
||||
if(links_changed) {
|
||||
// a link has been added or removed since the last update.
|
||||
// rebuild our cached list of records to (maybe) process.
|
||||
|
||||
scan_records.clear();
|
||||
scan_check_passive.clear();
|
||||
scan_changed.clear();
|
||||
|
||||
for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it)
|
||||
{
|
||||
pvaLink *link = *it;
|
||||
assert(link && link->alive);
|
||||
|
||||
if(!link->plink) continue;
|
||||
|
||||
// NPP and none/Default don't scan
|
||||
// PP, CP, and CPP do scan
|
||||
// PP and CPP only if SCAN=Passive
|
||||
if(link->pp != pvaLink::PP && link->pp != pvaLink::CPP && link->pp != pvaLink::CP)
|
||||
continue;
|
||||
|
||||
scan_records.push_back(link->plink->precord);
|
||||
scan_check_passive.push_back(link->pp != pvaLink::CP);
|
||||
scan_changed.push_back(link->proc_changed);
|
||||
}
|
||||
|
||||
DBManyLock ML(scan_records);
|
||||
|
||||
atomic_lock.swap(ML);
|
||||
|
||||
links_changed = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(scan_records.empty()) {
|
||||
// Nothing to do, so don't bother locking
|
||||
|
||||
} else if(isatomic && scan_records.size() > 1u) {
|
||||
DBManyLocker L(atomic_lock);
|
||||
|
||||
for(size_t i=0, N=scan_records.size(); i<N; i++) {
|
||||
run_dbProcess(i);
|
||||
}
|
||||
|
||||
} else {
|
||||
for(size_t i=0, N=scan_records.size(); i<N; i++) {
|
||||
DBScanLocker L(scan_records[i]);
|
||||
run_dbProcess(i);
|
||||
}
|
||||
}
|
||||
|
||||
if(requeue) {
|
||||
// re-queue until monitor queue is empty
|
||||
pvaGlobal->queue.add(shared_from_this());
|
||||
} else {
|
||||
run_done.signal();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace pvalink
|
||||
@@ -0,0 +1,321 @@
|
||||
#include <sstream>
|
||||
|
||||
#include <epicsStdio.h> // redirects stdout/stderr
|
||||
|
||||
#include "pvalink.h"
|
||||
|
||||
namespace pvalink {
|
||||
pvaLinkConfig::pvaLinkConfig()
|
||||
:queueSize(4)
|
||||
,pp(Default)
|
||||
,ms(NMS)
|
||||
,defer(false)
|
||||
,pipeline(false)
|
||||
,time(false)
|
||||
,retry(false)
|
||||
,local(false)
|
||||
,always(false)
|
||||
,monorder(0)
|
||||
{}
|
||||
pvaLinkConfig::~pvaLinkConfig() {}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace pvalink;
|
||||
|
||||
/* link options.
|
||||
*
|
||||
* "pvname" # short-hand, sets PV name only
|
||||
*
|
||||
* {
|
||||
* "pv":"name",
|
||||
* "field":"blah.foo",
|
||||
* "Q":5,
|
||||
* "pipeline":false,
|
||||
* "proc":true, // false, true, none, "", "NPP", "PP", "CP", "CPP"
|
||||
* "sevr":true, // false, true, "NMS", "MS", "MSI", "MSS"
|
||||
* "time":true, // false, true
|
||||
* "monorder":#,// order of processing during CP scan
|
||||
* "defer":true,// whether to immediately start Put, or only queue value to be sent
|
||||
* "retry":true,// queue Put while disconnected, and retry on connect
|
||||
* "always":true,// CP/CPP updates always process a like, even if its input field hasn't changed
|
||||
* "local":false,// Require local channel
|
||||
* }
|
||||
*/
|
||||
|
||||
jlink* pva_alloc_jlink(short dbr)
|
||||
{
|
||||
try {
|
||||
TRACE();
|
||||
return new pvaLink;
|
||||
|
||||
}catch(std::exception& e){
|
||||
errlogPrintf("Error allocating pva link: %s\n", e.what());
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#define TRY pvaLinkConfig *pvt = static_cast<pvaLinkConfig*>(pjlink); (void)pvt; try
|
||||
#define CATCH(RET) catch(std::exception& e){ \
|
||||
errlogPrintf("Error in %s link: %s\n", __FUNCTION__, e.what()); \
|
||||
return RET; }
|
||||
|
||||
void pva_free_jlink(jlink *pjlink)
|
||||
{
|
||||
TRY {
|
||||
TRACE();
|
||||
delete pvt;
|
||||
}catch(std::exception& e){
|
||||
errlogPrintf("Error freeing pva link: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
jlif_result pva_parse_null(jlink *pjlink)
|
||||
{
|
||||
TRY {
|
||||
TRACE(<<pvt->jkey<<" ");
|
||||
if(pvt->parseDepth!=1) {
|
||||
// ignore
|
||||
} else if(pvt->jkey == "proc") {
|
||||
pvt->pp = pvaLinkConfig::Default;
|
||||
} else if(pvt->jkey == "sevr") {
|
||||
pvt->ms = pvaLinkConfig::NMS;
|
||||
} else if(pvt->jkey == "local") {
|
||||
pvt->local = false; // alias for local:false
|
||||
} else if(pvt->debug) {
|
||||
printf("pva link parsing unknown none depth=%u key=\"%s\"\n",
|
||||
pvt->parseDepth, pvt->jkey.c_str());
|
||||
}
|
||||
|
||||
pvt->jkey.clear();
|
||||
return jlif_continue;
|
||||
}CATCH(jlif_stop)
|
||||
}
|
||||
|
||||
jlif_result pva_parse_bool(jlink *pjlink, int val)
|
||||
{
|
||||
TRY {
|
||||
TRACE(<<pvt->jkey<<" "<<(val?"true":"false"));
|
||||
if(pvt->parseDepth!=1) {
|
||||
// ignore
|
||||
} else if(pvt->jkey == "proc") {
|
||||
pvt->pp = val ? pvaLinkConfig::PP : pvaLinkConfig::NPP;
|
||||
} else if(pvt->jkey == "sevr") {
|
||||
pvt->ms = val ? pvaLinkConfig::MS : pvaLinkConfig::NMS;
|
||||
} else if(pvt->jkey == "defer") {
|
||||
pvt->defer = !!val;
|
||||
} else if(pvt->jkey == "pipeline") {
|
||||
pvt->pipeline = !!val;
|
||||
} else if(pvt->jkey == "time") {
|
||||
pvt->time = !!val;
|
||||
} else if(pvt->jkey == "retry") {
|
||||
pvt->retry = !!val;
|
||||
} else if(pvt->jkey == "local") {
|
||||
pvt->local = !!val;
|
||||
} else if(pvt->jkey == "always") {
|
||||
pvt->always = !!val;
|
||||
} else if(pvt->debug) {
|
||||
printf("pva link parsing unknown integer depth=%u key=\"%s\" value=%s\n",
|
||||
pvt->parseDepth, pvt->jkey.c_str(), val ? "true" : "false");
|
||||
}
|
||||
|
||||
pvt->jkey.clear();
|
||||
return jlif_continue;
|
||||
}CATCH(jlif_stop)
|
||||
}
|
||||
|
||||
jlif_result pva_parse_integer(jlink *pjlink, long long val)
|
||||
{
|
||||
TRY {
|
||||
TRACE(<<pvt->jkey<<" "<<val);
|
||||
if(pvt->parseDepth!=1) {
|
||||
// ignore
|
||||
} else if(pvt->jkey == "Q") {
|
||||
pvt->queueSize = val < 1 ? 1 : size_t(val);
|
||||
} else if(pvt->jkey == "monorder") {
|
||||
pvt->monorder = std::max(-1024, std::min(int(val), 1024));
|
||||
} else if(pvt->debug) {
|
||||
printf("pva link parsing unknown integer depth=%u key=\"%s\" value=%lld\n",
|
||||
pvt->parseDepth, pvt->jkey.c_str(), val);
|
||||
}
|
||||
|
||||
pvt->jkey.clear();
|
||||
return jlif_continue;
|
||||
}CATCH(jlif_stop)
|
||||
}
|
||||
|
||||
jlif_result pva_parse_string(jlink *pjlink, const char *val, size_t len)
|
||||
{
|
||||
TRY{
|
||||
std::string sval(val, len);
|
||||
TRACE(<<pvt->jkey<<" "<<sval);
|
||||
if(pvt->parseDepth==0 || (pvt->parseDepth==1 && pvt->jkey=="pv")) {
|
||||
pvt->channelName = sval;
|
||||
|
||||
} else if(pvt->parseDepth > 1) {
|
||||
// ignore
|
||||
|
||||
} else if(pvt->jkey=="field") {
|
||||
pvt->fieldName = sval;
|
||||
|
||||
} else if(pvt->jkey=="proc") {
|
||||
if(sval.empty()) {
|
||||
pvt->pp = pvaLinkConfig::Default;
|
||||
} else if(sval=="CP") {
|
||||
pvt->pp = pvaLinkConfig::CP;
|
||||
} else if(sval=="CPP") {
|
||||
pvt->pp = pvaLinkConfig::CPP;
|
||||
} else if(sval=="PP") {
|
||||
pvt->pp = pvaLinkConfig::PP;
|
||||
} else if(sval=="NPP") {
|
||||
pvt->pp = pvaLinkConfig::NPP;
|
||||
} else if(pvt->debug) {
|
||||
printf("pva link parsing unknown proc depth=%u key=\"%s\" value=\"%s\"\n",
|
||||
pvt->parseDepth, pvt->jkey.c_str(), sval.c_str());
|
||||
}
|
||||
|
||||
} else if(pvt->jkey=="sevr") {
|
||||
if(sval=="NMS") {
|
||||
pvt->ms = pvaLinkConfig::NMS;
|
||||
} else if(sval=="MS") {
|
||||
pvt->ms = pvaLinkConfig::MS;
|
||||
} else if(sval=="MSI") {
|
||||
pvt->ms = pvaLinkConfig::MSI;
|
||||
} else if(sval=="MSS") {
|
||||
// not sure how to handle mapping severity for MSS.
|
||||
// leave room for this to happen compatibly later by
|
||||
// handling as alias for MS until then.
|
||||
pvt->ms = pvaLinkConfig::MS;
|
||||
} else if(pvt->debug) {
|
||||
printf("pva link parsing unknown sevr depth=%u key=\"%s\" value=\"%s\"\n",
|
||||
pvt->parseDepth, pvt->jkey.c_str(), sval.c_str());
|
||||
}
|
||||
|
||||
} else if(pvt->debug) {
|
||||
printf("pva link parsing unknown string depth=%u key=\"%s\" value=\"%s\"\n",
|
||||
pvt->parseDepth, pvt->jkey.c_str(), sval.c_str());
|
||||
}
|
||||
|
||||
pvt->jkey.clear();
|
||||
return jlif_continue;
|
||||
}CATCH(jlif_stop)
|
||||
}
|
||||
|
||||
jlif_key_result pva_parse_start_map(jlink *pjlink)
|
||||
{
|
||||
TRY {
|
||||
TRACE();
|
||||
return jlif_key_continue;
|
||||
}CATCH(jlif_key_stop)
|
||||
}
|
||||
|
||||
jlif_result pva_parse_key_map(jlink *pjlink, const char *key, size_t len)
|
||||
{
|
||||
TRY {
|
||||
std::string sval(key, len);
|
||||
TRACE(<<sval);
|
||||
pvt->jkey = sval;
|
||||
|
||||
return jlif_continue;
|
||||
}CATCH(jlif_stop)
|
||||
}
|
||||
|
||||
jlif_result pva_parse_end_map(jlink *pjlink)
|
||||
{
|
||||
TRY {
|
||||
TRACE();
|
||||
return jlif_continue;
|
||||
}CATCH(jlif_stop)
|
||||
}
|
||||
|
||||
struct lset* pva_get_lset(const jlink *pjlink)
|
||||
{
|
||||
TRACE();
|
||||
return &pva_lset;
|
||||
}
|
||||
|
||||
void pva_report(const jlink *rpjlink, int lvl, int indent)
|
||||
{
|
||||
const pvaLink *pval = static_cast<const pvaLink*>(rpjlink);
|
||||
try {
|
||||
(void)pval;
|
||||
printf("%*s'pva': %s", indent, "", pval->channelName.c_str());
|
||||
if(!pval->fieldName.empty())
|
||||
printf("|.%s", pval->fieldName.c_str());
|
||||
|
||||
switch(pval->pp) {
|
||||
case pvaLinkConfig::NPP: printf(" NPP"); break;
|
||||
case pvaLinkConfig::Default: printf(" Def"); break;
|
||||
case pvaLinkConfig::PP: printf(" PP"); break;
|
||||
case pvaLinkConfig::CP: printf(" CP"); break;
|
||||
case pvaLinkConfig::CPP: printf(" CPP"); break;
|
||||
}
|
||||
switch(pval->ms) {
|
||||
case pvaLinkConfig::NMS: printf(" NMS"); break;
|
||||
case pvaLinkConfig::MS: printf(" MS"); break;
|
||||
case pvaLinkConfig::MSI: printf(" MSI"); break;
|
||||
}
|
||||
if(lvl>0) {
|
||||
printf(" Q=%u pipe=%c defer=%c time=%c retry=%c morder=%d",
|
||||
unsigned(pval->queueSize),
|
||||
pval->pipeline ? 'T' : 'F',
|
||||
pval->defer ? 'T' : 'F',
|
||||
pval->time ? 'T' : 'F',
|
||||
pval->retry ? 'T' : 'F',
|
||||
pval->monorder);
|
||||
}
|
||||
|
||||
if(pval->lchan) {
|
||||
// after open()
|
||||
Guard G(pval->lchan->lock);
|
||||
|
||||
printf(" conn=%c", pval->lchan->connected ? 'T' : 'F');
|
||||
if(pval->lchan->op_put.valid()) {
|
||||
printf(" Put");
|
||||
}
|
||||
|
||||
if(lvl>0) {
|
||||
printf(" #disconn=%zu prov=%s", pval->lchan->num_disconnect, pval->lchan->providerName.c_str());
|
||||
}
|
||||
if(lvl>1) {
|
||||
printf(" inprog=%c",
|
||||
pval->lchan->queued?'T':'F');
|
||||
}
|
||||
if(lvl>5) {
|
||||
std::ostringstream strm;
|
||||
pval->lchan->chan.show(strm);
|
||||
printf("\n%*s CH: %s", indent, "", strm.str().c_str());
|
||||
}
|
||||
} else {
|
||||
printf(" No Channel");
|
||||
}
|
||||
printf("\n");
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
} //namespace
|
||||
|
||||
namespace pvalink {
|
||||
|
||||
jlif lsetPVA = {
|
||||
"pva",
|
||||
&pva_alloc_jlink,
|
||||
&pva_free_jlink,
|
||||
&pva_parse_null,
|
||||
&pva_parse_bool,
|
||||
&pva_parse_integer,
|
||||
NULL,
|
||||
&pva_parse_string,
|
||||
&pva_parse_start_map,
|
||||
&pva_parse_key_map,
|
||||
&pva_parse_end_map,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
&pva_get_lset,
|
||||
&pva_report,
|
||||
NULL
|
||||
};
|
||||
|
||||
} //namespace pvalink
|
||||
@@ -0,0 +1,153 @@
|
||||
#include <pv/reftrack.h>
|
||||
#include <alarm.h>
|
||||
|
||||
#include "pvalink.h"
|
||||
|
||||
namespace pvalink {
|
||||
|
||||
pvaLink::pvaLink()
|
||||
:alive(true)
|
||||
,plink(0)
|
||||
,used_scratch(false)
|
||||
,used_queue(false)
|
||||
{
|
||||
REFTRACE_INCREMENT(num_instances);
|
||||
|
||||
snap_severity = INVALID_ALARM;
|
||||
snap_time.secPastEpoch = 0;
|
||||
snap_time.nsec = 0;
|
||||
|
||||
//TODO: valgrind tells me these aren't initialized by Base, but probably should be.
|
||||
parseDepth = 0;
|
||||
parent = 0;
|
||||
}
|
||||
|
||||
pvaLink::~pvaLink()
|
||||
{
|
||||
alive = false;
|
||||
|
||||
if(lchan) { // may be NULL if parsing fails
|
||||
Guard G(lchan->lock);
|
||||
|
||||
lchan->links.erase(this);
|
||||
lchan->links_changed = true;
|
||||
|
||||
bool new_debug = false;
|
||||
for(pvaLinkChannel::links_t::const_iterator it(lchan->links.begin()), end(lchan->links.end())
|
||||
; it!=end; ++it)
|
||||
{
|
||||
const pvaLink *pval = *it;
|
||||
if(pval->debug) {
|
||||
new_debug = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lchan->debug = new_debug;
|
||||
}
|
||||
|
||||
REFTRACE_DECREMENT(num_instances);
|
||||
}
|
||||
|
||||
static
|
||||
pvd::StructureConstPtr monitorRequestType = pvd::getFieldCreate()->createFieldBuilder()
|
||||
->addNestedStructure("field")
|
||||
->endNested()
|
||||
->addNestedStructure("record")
|
||||
->addNestedStructure("_options")
|
||||
->add("pipeline", pvd::pvBoolean)
|
||||
->add("atomic", pvd::pvBoolean)
|
||||
->add("queueSize", pvd::pvUInt)
|
||||
->endNested()
|
||||
->endNested()
|
||||
->createStructure();
|
||||
|
||||
pvd::PVStructurePtr pvaLink::makeRequest()
|
||||
{
|
||||
pvd::PVStructurePtr ret(pvd::getPVDataCreate()->createPVStructure(monitorRequestType));
|
||||
ret->getSubFieldT<pvd::PVBoolean>("record._options.pipeline")->put(pipeline);
|
||||
ret->getSubFieldT<pvd::PVBoolean>("record._options.atomic")->put(true);
|
||||
ret->getSubFieldT<pvd::PVUInt>("record._options.queueSize")->put(queueSize);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// caller must lock lchan->lock
|
||||
bool pvaLink::valid() const
|
||||
{
|
||||
return lchan->connected_latched && lchan->op_mon.root;
|
||||
}
|
||||
|
||||
// caller must lock lchan->lock
|
||||
pvd::PVField::const_shared_pointer pvaLink::getSubField(const char *name)
|
||||
{
|
||||
pvd::PVField::const_shared_pointer ret;
|
||||
if(valid()) {
|
||||
if(fieldName.empty()) {
|
||||
// we access the top level struct
|
||||
ret = lchan->op_mon.root->getSubField(name);
|
||||
|
||||
} else {
|
||||
// we access a sub-struct
|
||||
ret = lchan->op_mon.root->getSubField(fieldName);
|
||||
if(ret->getField()->getType()!=pvd::structure) {
|
||||
// addressed sub-field isn't a sub-structure
|
||||
if(strcmp(name, "value")!=0) {
|
||||
// unless we are trying to fetch the "value", we fail here
|
||||
ret.reset();
|
||||
}
|
||||
} else {
|
||||
ret = static_cast<const pvd::PVStructure*>(ret.get())->getSubField(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// call with channel lock held
|
||||
void pvaLink::onDisconnect()
|
||||
{
|
||||
DEBUG(this,<<plink->precord->name<<" disconnect");
|
||||
// TODO: option to remain queue'd while disconnected
|
||||
|
||||
used_queue = used_scratch = false;
|
||||
}
|
||||
|
||||
void pvaLink::onTypeChange()
|
||||
{
|
||||
DEBUG(this,<<plink->precord->name<<" type change");
|
||||
|
||||
assert(lchan->connected_latched && !!lchan->op_mon.root); // we should only be called when connected
|
||||
|
||||
fld_value = getSubField("value");
|
||||
fld_seconds = std::tr1::dynamic_pointer_cast<const pvd::PVScalar>(getSubField("timeStamp.secondsPastEpoch"));
|
||||
fld_nanoseconds = std::tr1::dynamic_pointer_cast<const pvd::PVScalar>(getSubField("timeStamp.nanoseconds"));
|
||||
fld_severity = std::tr1::dynamic_pointer_cast<const pvd::PVScalar>(getSubField("alarm.severity"));
|
||||
fld_display = std::tr1::dynamic_pointer_cast<const pvd::PVStructure>(getSubField("display"));
|
||||
fld_control = std::tr1::dynamic_pointer_cast<const pvd::PVStructure>(getSubField("control"));
|
||||
fld_valueAlarm = std::tr1::dynamic_pointer_cast<const pvd::PVStructure>(getSubField("valueAlarm"));
|
||||
|
||||
proc_changed.clear();
|
||||
|
||||
// build mask of all "changed" bits associated with our .value
|
||||
// CP/CPP input links will process this link only for updates where
|
||||
// the changed mask and proc_changed share at least one set bit.
|
||||
if(fld_value) {
|
||||
// bit for this field
|
||||
proc_changed.set(fld_value->getFieldOffset());
|
||||
|
||||
// bits of all parent fields
|
||||
for(const pvd::PVStructure* parent = fld_value->getParent(); parent; parent = parent->getParent()) {
|
||||
proc_changed.set(parent->getFieldOffset());
|
||||
}
|
||||
|
||||
if(fld_value->getField()->getType()==pvd::structure)
|
||||
{
|
||||
// bits of all child fields
|
||||
const pvd::PVStructure *val = static_cast<const pvd::PVStructure*>(fld_value.get());
|
||||
for(size_t i=val->getFieldOffset(), N=val->getNextFieldOffset(); i<N; i++)
|
||||
proc_changed.set(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace pvalink
|
||||
@@ -0,0 +1,493 @@
|
||||
|
||||
#include <epicsString.h>
|
||||
#include <alarm.h>
|
||||
#include <recGbl.h>
|
||||
#include <epicsStdio.h> // redirect stdout/stderr
|
||||
|
||||
#include <pv/current_function.h>
|
||||
|
||||
#include "pvalink.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace pvalink;
|
||||
|
||||
#define TRY pvaLink *self = static_cast<pvaLink*>(plink->value.json.jlink); assert(self->alive); try
|
||||
#define CATCH() catch(std::exception& e) { \
|
||||
errlogPrintf("pvaLink %s fails %s: %s\n", CURRENT_FUNCTION, plink->precord->name, e.what()); \
|
||||
}
|
||||
|
||||
#define CHECK_VALID() if(!self->valid()) { DEBUG(self, <<CURRENT_FUNCTION<<" "<<self->channelName<<" !valid"); return -1;}
|
||||
|
||||
void pvaOpenLink(DBLINK *plink)
|
||||
{
|
||||
try {
|
||||
pvaLink* self((pvaLink*)plink->value.json.jlink);
|
||||
|
||||
// workaround for Base not propagating info(base:lsetDebug to us
|
||||
{
|
||||
pdbRecordIterator rec(plink->precord);
|
||||
|
||||
if(epicsStrCaseCmp(rec.info("base:lsetDebug", "NO"), "YES")==0) {
|
||||
self->debug = 1;
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG(self, <<plink->precord->name<<" OPEN "<<self->channelName);
|
||||
|
||||
// still single threaded at this point.
|
||||
// also, no pvaLinkChannel::lock yet
|
||||
|
||||
self->plink = plink;
|
||||
|
||||
if(self->channelName.empty())
|
||||
return; // nothing to do...
|
||||
|
||||
pvd::PVStructure::const_shared_pointer pvRequest(self->makeRequest());
|
||||
pvaGlobal_t::channels_key_t key;
|
||||
|
||||
{
|
||||
std::ostringstream strm;
|
||||
strm<<*pvRequest; // print the request as a convient key for our channel cache
|
||||
|
||||
key = std::make_pair(self->channelName, strm.str());
|
||||
}
|
||||
|
||||
std::tr1::shared_ptr<pvaLinkChannel> chan;
|
||||
bool doOpen = false;
|
||||
{
|
||||
Guard G(pvaGlobal->lock);
|
||||
|
||||
pvaGlobal_t::channels_t::iterator it(pvaGlobal->channels.find(key));
|
||||
|
||||
if(it!=pvaGlobal->channels.end()) {
|
||||
// re-use existing channel
|
||||
chan = it->second.lock();
|
||||
}
|
||||
|
||||
if(!chan) {
|
||||
// open new channel
|
||||
|
||||
chan.reset(new pvaLinkChannel(key, pvRequest));
|
||||
pvaGlobal->channels.insert(std::make_pair(key, chan));
|
||||
doOpen = true;
|
||||
}
|
||||
|
||||
doOpen &= pvaGlobal->running; // if not running, then open from initHook
|
||||
}
|
||||
|
||||
if(doOpen) {
|
||||
chan->open(); // start subscription
|
||||
}
|
||||
|
||||
if(!self->local || chan->providerName=="QSRV"){
|
||||
Guard G(chan->lock);
|
||||
|
||||
chan->links.insert(self);
|
||||
chan->links_changed = true;
|
||||
|
||||
self->lchan.swap(chan); // we are now attached
|
||||
|
||||
self->lchan->debug |= !!self->debug;
|
||||
} else {
|
||||
// TODO: only print duing iocInit()?
|
||||
fprintf(stderr, "%s Error: local:true link to '%s' can't be fulfilled\n",
|
||||
plink->precord->name, self->channelName.c_str());
|
||||
plink->lset = NULL;
|
||||
}
|
||||
|
||||
return;
|
||||
}CATCH()
|
||||
// on error, prevent any further calls to our lset functions
|
||||
plink->lset = NULL;
|
||||
}
|
||||
|
||||
void pvaRemoveLink(struct dbLocker *locker, DBLINK *plink)
|
||||
{
|
||||
try {
|
||||
p2p::auto_ptr<pvaLink> self((pvaLink*)plink->value.json.jlink);
|
||||
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName);
|
||||
assert(self->alive);
|
||||
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
int pvaIsConnected(const DBLINK *plink)
|
||||
{
|
||||
TRY {
|
||||
Guard G(self->lchan->lock);
|
||||
|
||||
bool ret = self->valid();
|
||||
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<ret);
|
||||
return ret;
|
||||
|
||||
}CATCH()
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pvaGetDBFtype(const DBLINK *plink)
|
||||
{
|
||||
TRY {
|
||||
Guard G(self->lchan->lock);
|
||||
CHECK_VALID();
|
||||
|
||||
// if fieldName is empty, use top struct value
|
||||
// if fieldName not empty
|
||||
// if sub-field is struct, use sub-struct .value
|
||||
// if sub-field not struct, treat as value
|
||||
|
||||
pvd::PVField::const_shared_pointer value(self->getSubField("value"));
|
||||
|
||||
pvd::ScalarType ftype = pvd::pvInt; // default for un-mapable types.
|
||||
if(!value) {
|
||||
// no-op
|
||||
} else if(value->getField()->getType()==pvd::scalar)
|
||||
ftype = static_cast<const pvd::Scalar*>(value->getField().get())->getScalarType();
|
||||
else if(value->getField()->getType()==pvd::scalarArray)
|
||||
ftype = static_cast<const pvd::ScalarArray*>(value->getField().get())->getElementType();
|
||||
|
||||
int ret;
|
||||
switch(ftype) {
|
||||
#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case pvd::pv##PVACODE: ret = DBF_##DBFTYPE;
|
||||
#define CASE_REAL_INT64
|
||||
#include "pv/typemap.h"
|
||||
#undef CASE_REAL_INT64
|
||||
#undef CASE
|
||||
case pvd::pvString: ret = DBF_STRING; // TODO: long string?
|
||||
}
|
||||
|
||||
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<dbGetFieldTypeString(ret));
|
||||
return ret;
|
||||
|
||||
}CATCH()
|
||||
return -1;
|
||||
}
|
||||
|
||||
long pvaGetElements(const DBLINK *plink, long *nelements)
|
||||
{
|
||||
TRY {
|
||||
Guard G(self->lchan->lock);
|
||||
CHECK_VALID();
|
||||
|
||||
long ret = 0;
|
||||
if(self->fld_value && self->fld_value->getField()->getType()==pvd::scalarArray)
|
||||
ret = static_cast<const pvd::PVScalarArray*>(self->fld_value.get())->getLength();
|
||||
|
||||
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<ret);
|
||||
|
||||
return ret;
|
||||
}CATCH()
|
||||
return -1;
|
||||
}
|
||||
|
||||
long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer,
|
||||
long *pnRequest)
|
||||
{
|
||||
TRY {
|
||||
Guard G(self->lchan->lock);
|
||||
|
||||
if(!self->valid()) {
|
||||
// disconnected
|
||||
if(self->ms != pvaLink::NMS) {
|
||||
recGblSetSevr(plink->precord, LINK_ALARM, self->snap_severity);
|
||||
}
|
||||
// TODO: better capture of disconnect time
|
||||
epicsTimeGetCurrent(&self->snap_time);
|
||||
if(self->time) {
|
||||
plink->precord->time = self->snap_time;
|
||||
}
|
||||
DEBUG(self, <<CURRENT_FUNCTION<<" "<<self->channelName<<" !valid");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(self->fld_value) {
|
||||
long status = copyPVD2DBF(self->fld_value, pbuffer, dbrType, pnRequest);
|
||||
if(status) {
|
||||
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<status);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
if(self->fld_seconds) {
|
||||
self->snap_time.secPastEpoch = self->fld_seconds->getAs<pvd::uint32>() - POSIX_TIME_AT_EPICS_EPOCH;
|
||||
if(self->fld_nanoseconds) {
|
||||
self->snap_time.nsec = self->fld_nanoseconds->getAs<pvd::uint32>();
|
||||
} else {
|
||||
self->snap_time.nsec = 0u;
|
||||
}
|
||||
} else {
|
||||
self->snap_time.secPastEpoch = 0u;
|
||||
self->snap_time.nsec = 0u;
|
||||
}
|
||||
|
||||
if(self->fld_severity) {
|
||||
self->snap_severity = self->fld_severity->getAs<pvd::uint16>();
|
||||
} else {
|
||||
self->snap_severity = NO_ALARM;
|
||||
}
|
||||
|
||||
if((self->snap_severity!=NO_ALARM && self->ms == pvaLink::MS) ||
|
||||
(self->snap_severity==INVALID_ALARM && self->ms == pvaLink::MSI))
|
||||
{
|
||||
recGblSetSevr(plink->precord, LINK_ALARM, self->snap_severity);
|
||||
}
|
||||
|
||||
if(self->time) {
|
||||
plink->precord->time = self->snap_time;
|
||||
}
|
||||
|
||||
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" OK");
|
||||
return 0;
|
||||
}CATCH()
|
||||
return -1;
|
||||
}
|
||||
|
||||
long pvaGetControlLimits(const DBLINK *plink, double *lo, double *hi)
|
||||
{
|
||||
TRY {
|
||||
Guard G(self->lchan->lock);
|
||||
CHECK_VALID();
|
||||
|
||||
if(self->fld_control) {
|
||||
pvd::PVScalar::const_shared_pointer value;
|
||||
if(lo) {
|
||||
value = std::tr1::static_pointer_cast<const pvd::PVScalar>(self->fld_control->getSubField("limitLow"));
|
||||
*lo = value ? value->getAs<double>() : 0.0;
|
||||
}
|
||||
if(hi) {
|
||||
value = std::tr1::static_pointer_cast<const pvd::PVScalar>(self->fld_control->getSubField("limitHigh"));
|
||||
*hi = value ? value->getAs<double>() : 0.0;
|
||||
}
|
||||
} else {
|
||||
*lo = *hi = 0.0;
|
||||
}
|
||||
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0));
|
||||
return 0;
|
||||
}CATCH()
|
||||
return -1;
|
||||
}
|
||||
|
||||
long pvaGetGraphicLimits(const DBLINK *plink, double *lo, double *hi)
|
||||
{
|
||||
TRY {
|
||||
Guard G(self->lchan->lock);
|
||||
CHECK_VALID();
|
||||
|
||||
if(self->fld_display) {
|
||||
pvd::PVScalar::const_shared_pointer value;
|
||||
if(lo) {
|
||||
value = std::tr1::static_pointer_cast<const pvd::PVScalar>(self->fld_display->getSubField("limitLow"));
|
||||
*lo = value ? value->getAs<double>() : 0.0;
|
||||
}
|
||||
if(hi) {
|
||||
value = std::tr1::static_pointer_cast<const pvd::PVScalar>(self->fld_display->getSubField("limitHigh"));
|
||||
*hi = value ? value->getAs<double>() : 0.0;
|
||||
}
|
||||
} else {
|
||||
*lo = *hi = 0.0;
|
||||
}
|
||||
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0));
|
||||
return 0;
|
||||
}CATCH()
|
||||
return -1;
|
||||
}
|
||||
|
||||
long pvaGetAlarmLimits(const DBLINK *plink, double *lolo, double *lo,
|
||||
double *hi, double *hihi)
|
||||
{
|
||||
TRY {
|
||||
//Guard G(self->lchan->lock);
|
||||
//CHECK_VALID();
|
||||
*lolo = *lo = *hi = *hihi = 0.0;
|
||||
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<(lolo ? *lolo : 0)<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0)<<" "<<(hihi ? *hihi : 0));
|
||||
return 0;
|
||||
}CATCH()
|
||||
return -1;
|
||||
}
|
||||
|
||||
long pvaGetPrecision(const DBLINK *plink, short *precision)
|
||||
{
|
||||
TRY {
|
||||
//Guard G(self->lchan->lock);
|
||||
//CHECK_VALID();
|
||||
|
||||
// No sane way to recover precision from display.format string.
|
||||
*precision = 0;
|
||||
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<precision);
|
||||
return 0;
|
||||
}CATCH()
|
||||
return -1;
|
||||
}
|
||||
|
||||
long pvaGetUnits(const DBLINK *plink, char *units, int unitsSize)
|
||||
{
|
||||
TRY {
|
||||
Guard G(self->lchan->lock);
|
||||
CHECK_VALID();
|
||||
|
||||
if(unitsSize==0) return 0;
|
||||
|
||||
if(units && self->fld_display) {
|
||||
pvd::PVString::const_shared_pointer value(std::tr1::static_pointer_cast<const pvd::PVString>(self->fld_display->getSubField("units")));
|
||||
if(value) {
|
||||
const std::string& egu = value->get();
|
||||
strncpy(units, egu.c_str(), unitsSize);
|
||||
}
|
||||
} else if(units) {
|
||||
units[0] = '\0';
|
||||
}
|
||||
units[unitsSize-1] = '\0';
|
||||
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<units);
|
||||
return 0;
|
||||
}CATCH()
|
||||
return -1;
|
||||
}
|
||||
|
||||
long pvaGetAlarm(const DBLINK *plink, epicsEnum16 *status,
|
||||
epicsEnum16 *severity)
|
||||
{
|
||||
TRY {
|
||||
Guard G(self->lchan->lock);
|
||||
CHECK_VALID();
|
||||
|
||||
if(severity) {
|
||||
*severity = self->snap_severity;
|
||||
}
|
||||
if(status) {
|
||||
*status = self->snap_severity ? LINK_ALARM : NO_ALARM;
|
||||
}
|
||||
|
||||
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<(severity ? *severity : 0)<<" "<<(status ? *status : 0));
|
||||
return 0;
|
||||
}CATCH()
|
||||
return -1;
|
||||
}
|
||||
|
||||
long pvaGetTimeStamp(const DBLINK *plink, epicsTimeStamp *pstamp)
|
||||
{
|
||||
TRY {
|
||||
Guard G(self->lchan->lock);
|
||||
CHECK_VALID();
|
||||
|
||||
if(pstamp) {
|
||||
*pstamp = self->snap_time;
|
||||
}
|
||||
|
||||
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<(pstamp ? pstamp->secPastEpoch : 0)<<":"<<(pstamp ? pstamp->nsec: 0));
|
||||
return 0;
|
||||
}CATCH()
|
||||
return -1;
|
||||
}
|
||||
|
||||
// note that we handle DBF_ENUM differently than in pvif.cpp
|
||||
pvd::ScalarType DBR2PVD(short dbr)
|
||||
{
|
||||
switch(dbr) {
|
||||
#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case DBR_##DBFTYPE: return pvd::pv##PVACODE;
|
||||
#define CASE_SKIP_BOOL
|
||||
#define CASE_REAL_INT64
|
||||
#include "pv/typemap.h"
|
||||
#undef CASE_SKIP_BOOL
|
||||
#undef CASE_REAL_INT64
|
||||
#undef CASE
|
||||
case DBF_ENUM: return pvd::pvUShort;
|
||||
case DBF_STRING: return pvd::pvString;
|
||||
}
|
||||
throw std::invalid_argument("Unsupported DBR code");
|
||||
}
|
||||
|
||||
long pvaPutValue(DBLINK *plink, short dbrType,
|
||||
const void *pbuffer, long nRequest)
|
||||
{
|
||||
TRY {
|
||||
(void)self;
|
||||
Guard G(self->lchan->lock);
|
||||
|
||||
if(nRequest < 0) return -1;
|
||||
|
||||
if(!self->retry && !self->valid()) {
|
||||
DEBUG(self, <<CURRENT_FUNCTION<<" "<<self->channelName<<" !valid");
|
||||
return -1;
|
||||
}
|
||||
|
||||
pvd::ScalarType stype = DBR2PVD(dbrType);
|
||||
|
||||
pvd::shared_vector<const void> buf;
|
||||
|
||||
if(dbrType == DBF_STRING) {
|
||||
const char *sbuffer = (const char*)pbuffer;
|
||||
pvd::shared_vector<std::string> sval(nRequest);
|
||||
|
||||
for(long n=0; n<nRequest; n++, sbuffer += MAX_STRING_SIZE) {
|
||||
sval[n] = std::string(sbuffer, epicsStrnLen(sbuffer, MAX_STRING_SIZE));
|
||||
}
|
||||
|
||||
self->put_scratch = pvd::static_shared_vector_cast<const void>(pvd::freeze(sval));
|
||||
|
||||
} else {
|
||||
pvd::shared_vector<void> val(pvd::ScalarTypeFunc::allocArray(stype, size_t(nRequest)));
|
||||
|
||||
assert(size_t(dbValueSize(dbrType)*nRequest) == val.size());
|
||||
|
||||
memcpy(val.data(), pbuffer, val.size());
|
||||
|
||||
self->put_scratch = pvd::freeze(val);
|
||||
}
|
||||
|
||||
self->used_scratch = true;
|
||||
|
||||
if(!self->defer) self->lchan->put();
|
||||
|
||||
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<self->lchan->op_put.valid());
|
||||
return 0;
|
||||
}CATCH()
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pvaScanForward(DBLINK *plink)
|
||||
{
|
||||
TRY {
|
||||
Guard G(self->lchan->lock);
|
||||
|
||||
if(!self->retry && !self->valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// FWD_LINK is never deferred, and always results in a Put
|
||||
self->lchan->put(true);
|
||||
|
||||
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<self->lchan->op_put.valid());
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
#undef TRY
|
||||
#undef CATCH
|
||||
|
||||
} //namespace
|
||||
|
||||
namespace pvalink {
|
||||
|
||||
lset pva_lset = {
|
||||
0, 1, // non-const, volatile
|
||||
&pvaOpenLink,
|
||||
&pvaRemoveLink,
|
||||
NULL, NULL, NULL,
|
||||
&pvaIsConnected,
|
||||
&pvaGetDBFtype,
|
||||
&pvaGetElements,
|
||||
&pvaGetValue,
|
||||
&pvaGetControlLimits,
|
||||
&pvaGetGraphicLimits,
|
||||
&pvaGetAlarmLimits,
|
||||
&pvaGetPrecision,
|
||||
&pvaGetUnits,
|
||||
&pvaGetAlarm,
|
||||
&pvaGetTimeStamp,
|
||||
&pvaPutValue,
|
||||
NULL,
|
||||
&pvaScanForward
|
||||
//&pvaReportLink,
|
||||
};
|
||||
|
||||
} //namespace pvalink
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
#include <epicsExport.h>
|
||||
|
||||
static void installPVAAddLinkHook() {}
|
||||
|
||||
struct jlif {} lsetPVA;
|
||||
|
||||
extern "C" {
|
||||
int pvaLinkDebug;
|
||||
int pvaLinkNWorkers;
|
||||
|
||||
epicsExportRegistrar(installPVAAddLinkHook);
|
||||
epicsExportAddress(jlif, lsetPVA);
|
||||
epicsExportAddress(int, pvaLinkDebug);
|
||||
epicsExportAddress(int, pvaLinkNWorkers);
|
||||
}
|
||||
+300
-138
@@ -11,14 +11,18 @@
|
||||
#include <alarm.h>
|
||||
#include <errSymTbl.h>
|
||||
#include <epicsVersion.h>
|
||||
#include <errlog.h>
|
||||
#include <osiSock.h>
|
||||
|
||||
#include <pv/status.h>
|
||||
#include <pv/bitSet.h>
|
||||
#include <pv/pvData.h>
|
||||
#include <pv/anyscalar.h>
|
||||
#include <pv/reftrack.h>
|
||||
#include <pv/pvAccess.h>
|
||||
#include <pv/security.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include "sb.h"
|
||||
#include "pvif.h"
|
||||
|
||||
#include <epicsExport.h>
|
||||
@@ -26,44 +30,32 @@
|
||||
#ifdef EPICS_VERSION_INT
|
||||
# if EPICS_VERSION_INT>=VERSION_INT(3,16,1,0)
|
||||
# define USE_INT64
|
||||
// effects all uses of pv/typemap.h
|
||||
# define CASE_REAL_INT64
|
||||
# endif
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
int qsrvDisableFormat = 1;
|
||||
}
|
||||
|
||||
namespace pvd = epics::pvData;
|
||||
namespace pva = epics::pvAccess;
|
||||
|
||||
DBCH::DBCH(dbChannel *ch) :chan(ch)
|
||||
{
|
||||
if(dbChannelOpen(chan)) {
|
||||
dbChannelDelete(chan);
|
||||
throw std::invalid_argument("Failed to open channel");
|
||||
}
|
||||
if(!chan)
|
||||
throw std::invalid_argument(std::string("Invalid channel ")+dbChannelName(ch));
|
||||
prepare();
|
||||
}
|
||||
|
||||
DBCH::DBCH(const std::string& name)
|
||||
:chan(dbChannelCreate(name.c_str()))
|
||||
{
|
||||
if(!chan)
|
||||
throw std::invalid_argument("Invalid channel");
|
||||
if(dbChannelOpen(chan)) {
|
||||
dbChannelDelete(chan);
|
||||
throw std::invalid_argument("Failed to open channel "+name);
|
||||
}
|
||||
prepare();
|
||||
}
|
||||
|
||||
DBCH::DBCH(const char *name)
|
||||
:chan(dbChannelCreate(name))
|
||||
void DBCH::prepare()
|
||||
{
|
||||
if(!chan)
|
||||
throw std::invalid_argument("Invalid channel");
|
||||
throw std::invalid_argument("NULL channel");
|
||||
if(dbChannelOpen(chan)) {
|
||||
dbChannelDelete(chan);
|
||||
throw std::invalid_argument(std::string("Failed to open channel ")+name);
|
||||
throw std::invalid_argument(SB()<<"Failed to open channel "<<dbChannelName(chan));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +69,97 @@ void DBCH::swap(DBCH& o)
|
||||
std::swap(chan, o.chan);
|
||||
}
|
||||
|
||||
void ASCred::update(const pva::ChannelRequester::shared_pointer& req)
|
||||
{
|
||||
pva::PeerInfo::const_shared_pointer info(req->getPeerInfo());
|
||||
std::string usertemp, hosttemp;
|
||||
|
||||
if(info && info->identified) {
|
||||
hosttemp = info->peer;
|
||||
if(info->authority=="ca") {
|
||||
usertemp = info->account;
|
||||
size_t sep = usertemp.find_last_of('/');
|
||||
if(sep != std::string::npos) {
|
||||
// prevent CA auth from claiming to be eg. "krb/someone.special"
|
||||
usertemp = usertemp.substr(sep+1);
|
||||
}
|
||||
|
||||
} else {
|
||||
usertemp = info->authority + "/" + info->account;
|
||||
}
|
||||
|
||||
const char role[] = "role/";
|
||||
|
||||
groups.resize(info->roles.size());
|
||||
size_t idx = 0u;
|
||||
for(pva::PeerInfo::roles_t::const_iterator it(info->roles.begin()), end(info->roles.end()); it!=end; ++it, idx++) {
|
||||
groups[idx].resize((*it).size()+sizeof(role)); // sizeof(role) includes trailing nil
|
||||
std::copy(role,
|
||||
role+sizeof(role)-1,
|
||||
groups[idx].begin());
|
||||
std::copy(it->begin(),
|
||||
it->end(),
|
||||
groups[idx].begin()+sizeof(role)-1);
|
||||
groups[idx][groups[idx].size()-1] = '\0';
|
||||
}
|
||||
|
||||
} else {
|
||||
// legacy and anonymous
|
||||
hosttemp = req->getRequesterName();
|
||||
}
|
||||
|
||||
// remote names have the form "IP:port"
|
||||
size_t sep = hosttemp.find_first_of(':');
|
||||
if(sep == std::string::npos) {
|
||||
sep = hosttemp.size();
|
||||
}
|
||||
hosttemp.resize(sep);
|
||||
|
||||
host.resize(hosttemp.size()+1);
|
||||
std::copy(hosttemp.begin(),
|
||||
hosttemp.end(),
|
||||
host.begin());
|
||||
host[hosttemp.size()] = '\0';
|
||||
|
||||
user.resize(usertemp.size()+1);
|
||||
std::copy(usertemp.begin(),
|
||||
usertemp.end(),
|
||||
user.begin());
|
||||
user[usertemp.size()] = '\0';
|
||||
}
|
||||
|
||||
ASCLIENT::~ASCLIENT()
|
||||
{
|
||||
asRemoveClient(&aspvt);
|
||||
for(size_t i=0, N=grppvt.size(); i<N; i++) {
|
||||
asRemoveClient(&grppvt[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void ASCLIENT::add(dbChannel* chan, ASCred& cred)
|
||||
{
|
||||
asRemoveClient(&aspvt);
|
||||
/* asAddClient() fails secure to no-permission */
|
||||
(void)asAddClient(&aspvt, dbChannelRecord(chan)->asp, dbChannelFldDes(chan)->as_level, &cred.user[0], &cred.host[0]);
|
||||
|
||||
grppvt.resize(cred.groups.size(), 0);
|
||||
|
||||
for(size_t i=0, N=grppvt.size(); i<N; i++) {
|
||||
asRemoveClient(&grppvt[i]);
|
||||
(void)asAddClient(&grppvt[i], dbChannelRecord(chan)->asp, dbChannelFldDes(chan)->as_level, &cred.groups[i][0], &cred.host[0]);
|
||||
}
|
||||
}
|
||||
|
||||
bool ASCLIENT::canWrite() {
|
||||
if(!asActive || (aspvt && asCheckPut(aspvt)))
|
||||
return true;
|
||||
for(size_t i=0, N=grppvt.size(); i<N; i++) {
|
||||
if(grppvt[i] && asCheckPut(grppvt[i]))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
PVIF::PVIF(dbChannel *ch)
|
||||
:chan(ch)
|
||||
{}
|
||||
@@ -102,7 +185,8 @@ struct pvCommon : public pvTimeAlarm {
|
||||
pvd::BitSet maskVALUE, maskPROPERTY, maskVALUEPut;
|
||||
|
||||
pvd::PVDoublePtr displayLow, displayHigh, controlLow, controlHigh;
|
||||
pvd::PVStringPtr egu, desc, prec;
|
||||
pvd::PVStringPtr egu, desc;
|
||||
pvd::PVIntPtr fmt, prec;
|
||||
|
||||
pvd::PVScalarPtr warnLow, warnHigh, alarmLow, alarmHigh;
|
||||
|
||||
@@ -183,9 +267,32 @@ void attachTime(pvTimeAlarm& pvm, const pvd::PVStructurePtr& pv)
|
||||
#undef FMAP
|
||||
}
|
||||
|
||||
static
|
||||
pvd::shared_vector<const std::string> buildFormats()
|
||||
{
|
||||
pvd::shared_vector<std::string> fmt;
|
||||
fmt.push_back("Default");
|
||||
fmt.push_back("String");
|
||||
fmt.push_back("Binary");
|
||||
fmt.push_back("Decimal");
|
||||
fmt.push_back("Hex");
|
||||
fmt.push_back("Exponential");
|
||||
fmt.push_back("Engineering");
|
||||
return pvd::freeze(fmt);
|
||||
}
|
||||
|
||||
static const
|
||||
pvd::shared_vector<const std::string> displayForms(buildFormats());
|
||||
|
||||
// lookup fields and populate pvCommon. Non-existant fields will be NULL.
|
||||
void attachMeta(pvCommon& pvm, const pvd::PVStructurePtr& pv)
|
||||
{
|
||||
{
|
||||
pvd::PVStructurePtr fmt(pv->getSubField<pvd::PVStructure>("display.form"));
|
||||
if(fmt) {
|
||||
fmt->getSubFieldT<pvd::PVStringArray>("choices")->replace(displayForms);
|
||||
}
|
||||
}
|
||||
attachTime(pvm, pv);
|
||||
#define FMAP(MNAME, PVT, FNAME, DBE) pvm.MNAME = pv->getSubField<pvd::PVT>(FNAME); \
|
||||
if(pvm.MNAME) pvm.mask ## DBE.set(pvm.MNAME->getFieldOffset())
|
||||
@@ -195,7 +302,8 @@ void attachMeta(pvCommon& pvm, const pvd::PVStructurePtr& pv)
|
||||
FMAP(controlLow, PVDouble, "control.limitLow", PROPERTY);
|
||||
FMAP(egu, PVString, "display.units", PROPERTY);
|
||||
FMAP(desc, PVString, "display.description", PROPERTY);
|
||||
FMAP(prec, PVString, "display.format", PROPERTY);
|
||||
FMAP(prec, PVInt, "display.precision", PROPERTY);
|
||||
FMAP(fmt, PVInt, "display.form.index", PROPERTY);
|
||||
FMAP(warnHigh, PVScalar, "valueAlarm.highWarningLimit", PROPERTY);
|
||||
FMAP(warnLow, PVScalar, "valueAlarm.lowWarningLimit", PROPERTY);
|
||||
FMAP(alarmHigh, PVScalar, "valueAlarm.highAlarmLimit", PROPERTY);
|
||||
@@ -269,6 +377,18 @@ void mapStatus(unsigned code, pvd::PVInt* status, pvd::PVString* message)
|
||||
status->put(out);
|
||||
}
|
||||
|
||||
|
||||
template<typename META>
|
||||
void putMetaImpl(const pvTimeAlarm& pv, const META& meta)
|
||||
{
|
||||
pvd::int32 nsec = meta.time.nsec;
|
||||
if(pv.nsecMask) {
|
||||
pv.userTag->put(nsec&pv.nsecMask);
|
||||
nsec &= ~pv.nsecMask;
|
||||
}
|
||||
pv.nsec->put(nsec); pv.sec->put(meta.time.secPastEpoch+POSIX_TIME_AT_EPICS_EPOCH);
|
||||
}
|
||||
|
||||
void putTime(const pvTimeAlarm& pv, unsigned dbe, db_field_log *pfl)
|
||||
{
|
||||
metaTIME meta;
|
||||
@@ -278,12 +398,7 @@ void putTime(const pvTimeAlarm& pv, unsigned dbe, db_field_log *pfl)
|
||||
if(status)
|
||||
throw std::runtime_error("dbGet for meta fails");
|
||||
|
||||
pvd::int32 nsec = meta.time.nsec;
|
||||
if(pv.nsecMask) {
|
||||
pv.userTag->put(nsec&pv.nsecMask);
|
||||
nsec &= ~pv.nsecMask;
|
||||
}
|
||||
pv.nsec->put(nsec); pv.sec->put(meta.time.secPastEpoch+POSIX_TIME_AT_EPICS_EPOCH);
|
||||
putMetaImpl(pv, meta);
|
||||
if(dbe&DBE_ALARM) {
|
||||
mapStatus(meta.status, pv.status.get(), pv.message.get());
|
||||
pv.severity->put(meta.severity);
|
||||
@@ -299,6 +414,11 @@ void putValue(dbChannel *chan, pvd::PVScalar* value, db_field_log *pfl)
|
||||
if(status)
|
||||
throw std::runtime_error("dbGet for meta fails");
|
||||
|
||||
if(nReq==0) {
|
||||
// this was an actual max length 1 array, which has zero elements now.
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
}
|
||||
|
||||
switch(dbChannelFinalFieldType(chan)) {
|
||||
#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case DBR_##DBFTYPE: value->putFrom<PVATYPE>(buf.dbf_##DBFTYPE); break;
|
||||
#define CASE_ENUM
|
||||
@@ -412,7 +532,6 @@ void putValue(dbChannel *chan, pvd::PVScalarArray* value, db_field_log *pfl)
|
||||
value->putFrom(pvd::freeze(buf));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename META>
|
||||
void putMeta(const pvCommon& pv, unsigned dbe, db_field_log *pfl)
|
||||
{
|
||||
@@ -424,14 +543,8 @@ void putMeta(const pvCommon& pv, unsigned dbe, db_field_log *pfl)
|
||||
if(status)
|
||||
throw std::runtime_error("dbGet for meta fails");
|
||||
|
||||
pvd::int32 nsec = meta.time.nsec;
|
||||
if(pv.nsecMask) {
|
||||
pv.userTag->put(nsec&pv.nsecMask);
|
||||
nsec &= ~pv.nsecMask;
|
||||
}
|
||||
pv.nsec->put(nsec);
|
||||
putMetaImpl(pv, meta);
|
||||
#define FMAP(MNAME, FNAME) pv.MNAME->put(meta.FNAME)
|
||||
FMAP(sec, time.secPastEpoch+POSIX_TIME_AT_EPICS_EPOCH);
|
||||
if(dbe&DBE_ALARM) {
|
||||
mapStatus(meta.status, pv.status.get(), pv.message.get());
|
||||
FMAP(severity, severity);
|
||||
@@ -446,61 +559,12 @@ void putMeta(const pvCommon& pv, unsigned dbe, db_field_log *pfl)
|
||||
FMAP(DBR_CTRL_DOUBLE, controlLow, lower_ctrl_limit);
|
||||
FMAP(DBR_GR_DOUBLE, egu, units);
|
||||
#undef FMAP
|
||||
if(META::mask&DBR_PRECISION && pv.prec && !qsrvDisableFormat) {
|
||||
// construct printf() style format.
|
||||
// Widths based on epicsTypes.h
|
||||
char buf[8];
|
||||
char *pos = &buf[8]; // build string in reverse order
|
||||
bool ok = true;
|
||||
|
||||
*--pos = '\0'; // buf[7] = '\0'
|
||||
|
||||
switch(dbChannelFinalFieldType(pv.chan)) {
|
||||
#ifdef USE_INT64
|
||||
case DBF_UINT64:
|
||||
*--pos = 'l';
|
||||
*--pos = 'l';
|
||||
#endif
|
||||
case DBF_UCHAR:
|
||||
case DBF_USHORT:
|
||||
case DBF_ULONG:
|
||||
*--pos = 'u';
|
||||
break;
|
||||
#ifdef USE_INT64
|
||||
case DBF_INT64:
|
||||
*--pos = 'l';
|
||||
*--pos = 'l';
|
||||
#endif
|
||||
case DBF_CHAR:
|
||||
case DBF_SHORT:
|
||||
case DBF_LONG:
|
||||
*--pos = 'd';
|
||||
break;
|
||||
case DBF_DOUBLE:
|
||||
case DBF_FLOAT:
|
||||
*--pos = 'g'; // either decimal or scientific
|
||||
break;
|
||||
case DBF_STRING:
|
||||
*--pos = 's';
|
||||
break;
|
||||
default:
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if(ok) {
|
||||
long dp = meta.precision.dp;
|
||||
if(dp<0) dp = 0;
|
||||
else if(dp>=99) dp = 99;
|
||||
for(;dp;dp = dp/10u) {
|
||||
*--pos = '0'+(dp%10u);
|
||||
}
|
||||
*--pos = '%';
|
||||
}
|
||||
pv.prec->put(pos);
|
||||
if(META::mask&DBR_PRECISION && pv.prec) {
|
||||
pv.prec->put(pvd::int32(meta.precision.dp));
|
||||
}
|
||||
#define FMAP(MASK, MNAME, FNAME) if(META::mask&(MASK) && pv.MNAME) pv.MNAME->putFrom(meta.FNAME)
|
||||
// not handling precision until I get a better idea of what 'format' is supposed to be...
|
||||
//FMAP(prec, PVScalar, "display.format", PROPERTY);
|
||||
//FMAP(prec, PVScalar, "display.form", PROPERTY);
|
||||
FMAP(DBR_AL_DOUBLE, warnHigh, upper_warning_limit);
|
||||
FMAP(DBR_AL_DOUBLE, warnLow, lower_warning_limit);
|
||||
FMAP(DBR_AL_DOUBLE, alarmHigh, upper_alarm_limit);
|
||||
@@ -531,15 +595,15 @@ void putAll(const PVC &pv, unsigned dbe, db_field_log *pfl)
|
||||
}
|
||||
}
|
||||
|
||||
void findNSMask(pvTimeAlarm& pvmeta, dbChannel *chan, const epics::pvData::PVStructurePtr& pvalue)
|
||||
void findNSMask(pvTimeAlarm& pvmeta, pdbRecordIterator& info, const epics::pvData::PVStructurePtr& pvalue)
|
||||
{
|
||||
pdbRecordIterator info(chan);
|
||||
const char *UT = info.info("Q:time:tag");
|
||||
if(UT && strncmp(UT, "nsec:lsb:", 9)==0) {
|
||||
try{
|
||||
pvmeta.nsecMask = epics::pvData::castUnsafe<pvd::uint32>(std::string(&UT[9]));
|
||||
}catch(std::exception& e){
|
||||
std::cerr<<dbChannelRecord(chan)->name<<" : Q:time:tag nsec:lsb: requires a number not '"<<UT[9]<<"'\n";
|
||||
pvmeta.nsecMask = 0;
|
||||
std::cerr<<info.name()<<" : Q:time:tag nsec:lsb: requires a number not '"<<UT[9]<<"'\n";
|
||||
}
|
||||
}
|
||||
if(pvmeta.nsecMask>0 && pvmeta.nsecMask<=32) {
|
||||
@@ -555,6 +619,37 @@ void findNSMask(pvTimeAlarm& pvmeta, dbChannel *chan, const epics::pvData::PVStr
|
||||
pvmeta.nsecMask = 0;
|
||||
}
|
||||
|
||||
void findFormat(pvTimeAlarm& pvmeta, pdbRecordIterator& info, const epics::pvData::PVStructurePtr& pvalue)
|
||||
{
|
||||
const char *FMT = info.info("Q:form");
|
||||
if(FMT) {
|
||||
pvd::PVScalarPtr fmt(pvalue->getSubField<pvd::PVScalar>("display.form.index"));
|
||||
if(fmt) {
|
||||
bool found = false;
|
||||
for(size_t i=0; !found && i<displayForms.size(); i++) {
|
||||
if((found=(displayForms[i]==FMT)))
|
||||
fmt->putFrom<pvd::uint32>(i);
|
||||
}
|
||||
if(!found) {
|
||||
try {
|
||||
fmt->putFrom(std::string(FMT)); // attempt to parse as number
|
||||
}catch(std::exception& e){
|
||||
errlogPrintf("%s: info(Q:form, \"%s\") is not known format: %s\n", info.name(), FMT, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
struct PVIFScalarNumeric : public PVIF
|
||||
{
|
||||
@@ -585,7 +680,9 @@ struct PVIFScalarNumeric : public PVIF
|
||||
pvmeta.maskVALUEPut.set(0);
|
||||
pvmeta.maskVALUEPut.set(bit);
|
||||
}
|
||||
findNSMask(pvmeta, chan, pvalue);
|
||||
pdbRecordIterator info(chan);
|
||||
findNSMask(pvmeta, info, pvalue);
|
||||
findFormat(pvmeta, info, pvalue);
|
||||
}
|
||||
virtual ~PVIFScalarNumeric() {}
|
||||
|
||||
@@ -607,12 +704,24 @@ struct PVIFScalarNumeric : public PVIF
|
||||
}
|
||||
}
|
||||
|
||||
virtual pvd::Status get(const epics::pvData::BitSet& mask, proc_t proc) OVERRIDE FINAL
|
||||
virtual pvd::Status get(const epics::pvData::BitSet& mask, proc_t proc, bool permit) OVERRIDE FINAL
|
||||
{
|
||||
pvd::Status ret;
|
||||
if(mask.logical_and(pvmeta.maskVALUEPut)) {
|
||||
getValue(pvmeta.chan, pvmeta.value.get());
|
||||
ret = PVIF::get(mask, proc);
|
||||
pvd::Status ret = checkDISP(chan);
|
||||
if(!ret)
|
||||
return ret;
|
||||
|
||||
bool newval = mask.logical_and(pvmeta.maskVALUEPut);
|
||||
if(newval) {
|
||||
if(permit)
|
||||
getValue(pvmeta.chan, pvmeta.value.get());
|
||||
else
|
||||
ret = pvd::Status::error("Put not permitted");
|
||||
}
|
||||
if(newval || proc==PVIF::ProcForce) {
|
||||
if(permit)
|
||||
ret = PVIF::get(mask, proc);
|
||||
else
|
||||
ret = pvd::Status::error("Process not permitted");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -632,6 +741,7 @@ struct PVIFScalarNumeric : public PVIF
|
||||
|
||||
} // namespace
|
||||
|
||||
static
|
||||
pvd::ScalarType DBR2PVD(short dbr)
|
||||
{
|
||||
switch(dbr) {
|
||||
@@ -643,22 +753,25 @@ pvd::ScalarType DBR2PVD(short dbr)
|
||||
#undef CASE_SKIP_BOOL
|
||||
#undef CASE
|
||||
case DBF_STRING: return pvd::pvString;
|
||||
default:
|
||||
throw std::invalid_argument("Unsupported DBR code");
|
||||
}
|
||||
throw std::invalid_argument("Unsupported DBR code");
|
||||
}
|
||||
|
||||
short PVD2DBR(pvd::ScalarType pvt)
|
||||
{
|
||||
switch(pvt) {
|
||||
#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case pvd::pv##PVACODE: return DBR_##DBFTYPE;
|
||||
#define CASE_SQUEEZE_INT64
|
||||
#ifndef USE_INT64
|
||||
# define CASE_SQUEEZE_INT64
|
||||
#endif
|
||||
#include "pv/typemap.h"
|
||||
#undef CASE_SQUEEZE_INT64
|
||||
#ifndef USE_INT64
|
||||
# undef CASE_SQUEEZE_INT64
|
||||
#endif
|
||||
#undef CASE
|
||||
default:
|
||||
throw std::invalid_argument("Unsupported pvType code");
|
||||
case pvd::pvString: return DBF_STRING;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
epics::pvData::FieldConstPtr
|
||||
@@ -674,19 +787,46 @@ ScalarBuilder::dtype(dbChannel *channel)
|
||||
if(maxelem!=1 && dbr==DBR_ENUM)
|
||||
dbr = DBF_SHORT;
|
||||
|
||||
pvd::FieldBuilderPtr builder(pvd::getFieldCreate()->createFieldBuilder());
|
||||
pvd::StandardFieldPtr standard(pvd::getStandardField());
|
||||
|
||||
if(dbr==DBR_ENUM)
|
||||
return pvd::getStandardField()->enumerated("alarm,timeStamp");
|
||||
|
||||
std::string options;
|
||||
if(dbr!=DBR_STRING)
|
||||
options = "alarm,timeStamp,display,control,valueAlarm";
|
||||
builder = builder->setId("epics:nt/NTEnum:1.0")
|
||||
->addNestedStructure("value")
|
||||
->setId("enum_t")
|
||||
->add("index", pvd::pvInt)
|
||||
->addArray("choices", pvd::pvString)
|
||||
->endNested();
|
||||
else if(maxelem==1)
|
||||
builder = builder->setId("epics:nt/NTScalar:1.0")
|
||||
->add("value", pvt);
|
||||
else
|
||||
options = "alarm,timeStamp,display,control";
|
||||
builder = builder->setId("epics:nt/NTScalarArray:1.0")
|
||||
->addArray("value", pvt);
|
||||
|
||||
if(maxelem==1)
|
||||
return pvd::getStandardField()->scalar(pvt, options);
|
||||
else
|
||||
return pvd::getStandardField()->scalarArray(pvt, options);
|
||||
builder = builder->add("alarm", standard->alarm())
|
||||
->add("timeStamp", standard->timeStamp());
|
||||
|
||||
if(dbr!=DBR_ENUM) {
|
||||
builder = builder->addNestedStructure("display")
|
||||
->add("limitLow", pvd::pvDouble)
|
||||
->add("limitHigh", pvd::pvDouble)
|
||||
->add("description", pvd::pvString)
|
||||
->add("units", pvd::pvString)
|
||||
->add("precision", pvd::pvInt)
|
||||
->addNestedStructure("form")
|
||||
->setId("enum_t")
|
||||
->add("index", pvd::pvInt)
|
||||
->addArray("choices", pvd::pvString)
|
||||
->endNested()
|
||||
->endNested()
|
||||
->add("control", standard->control());
|
||||
|
||||
if(dbr!=DBR_STRING)
|
||||
builder = builder->add("valueAlarm", standard->doubleAlarm());
|
||||
}
|
||||
|
||||
return builder->createStructure();
|
||||
}
|
||||
|
||||
PVIF*
|
||||
@@ -699,7 +839,6 @@ ScalarBuilder::attach(dbChannel *channel, const epics::pvData::PVStructurePtr& r
|
||||
|
||||
const short dbr = dbChannelFinalFieldType(channel);
|
||||
const long maxelem = dbChannelFinalElements(channel);
|
||||
//const pvd::ScalarType pvt = DBR2PVD(dbr);
|
||||
|
||||
if(maxelem==1) {
|
||||
switch(dbr) {
|
||||
@@ -709,6 +848,10 @@ ScalarBuilder::attach(dbChannel *channel, const epics::pvData::PVStructurePtr& r
|
||||
case DBR_USHORT:
|
||||
case DBR_LONG:
|
||||
case DBR_ULONG:
|
||||
#ifdef USE_INT64
|
||||
case DBR_INT64:
|
||||
case DBR_UINT64:
|
||||
#endif
|
||||
return new PVIFScalarNumeric<pvScalar, metaDOUBLE>(channel, fld, enclosing);
|
||||
case DBR_FLOAT:
|
||||
case DBR_DOUBLE:
|
||||
@@ -729,6 +872,10 @@ ScalarBuilder::attach(dbChannel *channel, const epics::pvData::PVStructurePtr& r
|
||||
case DBR_ULONG:
|
||||
case DBR_STRING:
|
||||
case DBR_FLOAT:
|
||||
#ifdef USE_INT64
|
||||
case DBR_INT64:
|
||||
case DBR_UINT64:
|
||||
#endif
|
||||
case DBR_DOUBLE:
|
||||
return new PVIFScalarNumeric<pvArray, metaDOUBLE>(channel, fld, enclosing);
|
||||
}
|
||||
@@ -760,7 +907,7 @@ struct PVIFPlain : public PVIF
|
||||
|
||||
virtual ~PVIFPlain() {}
|
||||
|
||||
virtual void put(epics::pvData::BitSet& mask, unsigned dbe, db_field_log *pfl)
|
||||
virtual void put(epics::pvData::BitSet& mask, unsigned dbe, db_field_log *pfl) OVERRIDE FINAL
|
||||
{
|
||||
if(dbe&DBE_VALUE) {
|
||||
putValue(channel, field.get(), pfl);
|
||||
@@ -768,17 +915,29 @@ struct PVIFPlain : public PVIF
|
||||
}
|
||||
}
|
||||
|
||||
virtual pvd::Status get(const epics::pvData::BitSet& mask, proc_t proc)
|
||||
virtual pvd::Status get(const epics::pvData::BitSet& mask, proc_t proc, bool permit) OVERRIDE FINAL
|
||||
{
|
||||
pvd::Status ret;
|
||||
if(mask.get(fieldOffset)) {
|
||||
getValue(channel, field.get());
|
||||
ret = PVIF::get(mask, proc);
|
||||
pvd::Status ret = checkDISP(chan);
|
||||
if(!ret)
|
||||
return ret;
|
||||
|
||||
bool newval = mask.get(fieldOffset);
|
||||
if(newval) {
|
||||
if(permit)
|
||||
getValue(channel, field.get());
|
||||
else
|
||||
ret = pvd::Status::error("Put not permitted");
|
||||
}
|
||||
if(newval || proc==PVIF::ProcForce) {
|
||||
if(permit)
|
||||
ret = PVIF::get(mask, proc);
|
||||
else
|
||||
ret = pvd::Status::error("Process not permitted");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
virtual unsigned dbe(const epics::pvData::BitSet& mask)
|
||||
virtual unsigned dbe(const epics::pvData::BitSet& mask) OVERRIDE FINAL
|
||||
{
|
||||
// TODO: figure out how to handle various intermidiate compressed
|
||||
// bitSet and enclosing.
|
||||
@@ -888,8 +1047,10 @@ struct PVIFMeta : public PVIF
|
||||
if(!field)
|
||||
throw std::logic_error("PVIFMeta attached type mis-match");
|
||||
meta.chan = channel;
|
||||
pdbRecordIterator info(chan);
|
||||
attachTime(meta, field);
|
||||
findNSMask(meta, channel, field);
|
||||
findNSMask(meta, info, field);
|
||||
findFormat(meta, info, field);
|
||||
if(enclosing) {
|
||||
meta.maskALWAYS.clear();
|
||||
meta.maskALWAYS.set(enclosing->getFieldOffset());
|
||||
@@ -900,7 +1061,7 @@ struct PVIFMeta : public PVIF
|
||||
|
||||
virtual ~PVIFMeta() {}
|
||||
|
||||
virtual void put(epics::pvData::BitSet& mask, unsigned dbe, db_field_log *pfl)
|
||||
virtual void put(epics::pvData::BitSet& mask, unsigned dbe, db_field_log *pfl) OVERRIDE FINAL
|
||||
{
|
||||
mask |= meta.maskALWAYS;
|
||||
if(dbe&DBE_ALARM)
|
||||
@@ -909,13 +1070,15 @@ struct PVIFMeta : public PVIF
|
||||
putTime(meta, dbe, pfl);
|
||||
}
|
||||
|
||||
virtual pvd::Status get(const epics::pvData::BitSet& mask, proc_t proc)
|
||||
virtual pvd::Status get(const epics::pvData::BitSet& mask, proc_t proc, bool permit) OVERRIDE FINAL
|
||||
{
|
||||
// can't put time/alarm
|
||||
if(mask.logical_and(meta.maskALARM))
|
||||
return pvd::Status::warn("Put to meta field ignored");
|
||||
return pvd::Status::Ok;
|
||||
}
|
||||
|
||||
virtual unsigned dbe(const epics::pvData::BitSet& mask)
|
||||
virtual unsigned dbe(const epics::pvData::BitSet& mask) OVERRIDE FINAL
|
||||
{
|
||||
if(mask.logical_and(meta.maskALARM))
|
||||
return DBE_ALARM;
|
||||
@@ -970,18 +1133,18 @@ struct PVIFProc : public PVIF
|
||||
{
|
||||
PVIFProc(dbChannel *channel) :PVIF(channel) {}
|
||||
|
||||
virtual void put(epics::pvData::BitSet& mask, unsigned dbe, db_field_log *pfl)
|
||||
virtual void put(epics::pvData::BitSet& mask, unsigned dbe, db_field_log *pfl) OVERRIDE FINAL
|
||||
{
|
||||
// nothing to get
|
||||
}
|
||||
|
||||
virtual pvd::Status get(const epics::pvData::BitSet& mask, proc_t proc)
|
||||
virtual pvd::Status get(const epics::pvData::BitSet& mask, proc_t proc, bool permit) OVERRIDE FINAL
|
||||
{
|
||||
// always process
|
||||
return PVIF::get(mask, PVIF::ProcForce);
|
||||
// always process (if permitted)
|
||||
return PVIF::get(mask, PVIF::ProcForce, permit);
|
||||
}
|
||||
|
||||
virtual unsigned dbe(const epics::pvData::BitSet& mask)
|
||||
virtual unsigned dbe(const epics::pvData::BitSet& mask) OVERRIDE FINAL
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -996,7 +1159,7 @@ struct ProcBuilder : public PVIFBuilder
|
||||
|
||||
virtual epics::pvData::FieldBuilderPtr dtype(epics::pvData::FieldBuilderPtr& builder,
|
||||
const std::string& fld,
|
||||
dbChannel *channel)
|
||||
dbChannel *channel) OVERRIDE FINAL
|
||||
{
|
||||
// invisible
|
||||
return builder;
|
||||
@@ -1014,7 +1177,7 @@ struct ProcBuilder : public PVIFBuilder
|
||||
|
||||
}//namespace
|
||||
|
||||
pvd::Status PVIF::get(const epics::pvData::BitSet& mask, proc_t proc)
|
||||
pvd::Status PVIF::get(const epics::pvData::BitSet& mask, proc_t proc, bool permit)
|
||||
{
|
||||
dbCommon *precord = dbChannelRecord(chan);
|
||||
|
||||
@@ -1026,7 +1189,10 @@ pvd::Status PVIF::get(const epics::pvData::BitSet& mask, proc_t proc)
|
||||
pvd::Status ret;
|
||||
|
||||
if (tryproc) {
|
||||
if (precord->pact) {
|
||||
if (!permit) {
|
||||
return pvd::Status::error("Process not permitted");
|
||||
|
||||
} else if (precord->pact) {
|
||||
if (precord->tpro)
|
||||
printf("%s: Active %s\n",
|
||||
epicsThreadGetNameSelf(), precord->name);
|
||||
@@ -1078,7 +1244,3 @@ PVIFBuilder* PVIFBuilder::create(const std::string& type)
|
||||
else
|
||||
throw std::runtime_error(std::string("Unknown +type=")+type);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
epicsExportAddress(int, qsrvDisableFormat);
|
||||
}
|
||||
|
||||
+79
-14
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <asLib.h>
|
||||
#include <dbAccess.h>
|
||||
#include <dbChannel.h>
|
||||
#include <dbStaticLib.h>
|
||||
@@ -15,7 +16,7 @@
|
||||
#include <pv/pvData.h>
|
||||
#include <pv/anyscalar.h>
|
||||
|
||||
#include <shareLib.h>
|
||||
#include <pv/qsrv.h>
|
||||
|
||||
#ifndef VERSION_INT
|
||||
# define VERSION_INT(V,R,M,P) ( ((V)<<24) | ((R)<<16) | ((M)<<8) | (P))
|
||||
@@ -29,9 +30,25 @@
|
||||
# define USE_MULTILOCK
|
||||
#endif
|
||||
|
||||
epics::pvData::ScalarType DBR2PVD(short dbr);
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
class ChannelRequester;
|
||||
}
|
||||
}
|
||||
|
||||
short PVD2DBR(epics::pvData::ScalarType pvt);
|
||||
|
||||
// copy from PVField (.value sub-field) to DBF buffer
|
||||
QSRV_API
|
||||
long copyPVD2DBF(const epics::pvData::PVField::const_shared_pointer& in,
|
||||
void *outbuf, short outdbf, long *outnReq);
|
||||
// copy from DBF buffer to PVField (.value sub-field)
|
||||
QSRV_API
|
||||
long copyDBF2PVD(const epics::pvData::shared_vector<const void>& buf,
|
||||
const epics::pvData::PVField::shared_pointer& out,
|
||||
epics::pvData::BitSet &changed,
|
||||
const epics::pvData::PVStringArray::const_svector& choices);
|
||||
|
||||
union dbrbuf {
|
||||
epicsInt8 dbf_CHAR;
|
||||
epicsUInt8 dbf_UCHAR;
|
||||
@@ -42,15 +59,21 @@ union dbrbuf {
|
||||
epicsUInt32 dbf_ULONG;
|
||||
epicsFloat32 dbf_FLOAT;
|
||||
epicsFloat64 dbf_DOUBLE;
|
||||
|
||||
#ifdef EPICS_VERSION_INT
|
||||
# if EPICS_VERSION_INT>=VERSION_INT(3,16,1,0)
|
||||
epicsInt64 dbf_INT64;
|
||||
epicsUInt64 dbf_UINT64;
|
||||
# endif
|
||||
#endif
|
||||
char dbf_STRING[MAX_STRING_SIZE];
|
||||
};
|
||||
|
||||
struct epicsShareClass DBCH {
|
||||
struct QSRV_API DBCH {
|
||||
dbChannel *chan;
|
||||
DBCH() :chan(0) {}
|
||||
explicit DBCH(dbChannel *ch); // calls dbChannelOpen()
|
||||
explicit DBCH(const std::string& name);
|
||||
explicit DBCH(const char *name);
|
||||
~DBCH();
|
||||
|
||||
void swap(DBCH&);
|
||||
@@ -62,6 +85,24 @@ struct epicsShareClass DBCH {
|
||||
private:
|
||||
DBCH(const DBCH&);
|
||||
DBCH& operator=(const DBCH&);
|
||||
void prepare();
|
||||
};
|
||||
|
||||
struct ASCred {
|
||||
// string storage must be safely mutable. cf. asAddClient()
|
||||
std::vector<char> user, host;
|
||||
std::vector<std::vector<char> > groups;
|
||||
void update(const std::tr1::shared_ptr<epics::pvAccess::ChannelRequester>& request);
|
||||
};
|
||||
|
||||
struct ASCLIENT {
|
||||
ASCLIENTPVT aspvt;
|
||||
std::vector<ASCLIENTPVT> grppvt;
|
||||
ASCLIENT() :aspvt(0) {}
|
||||
~ASCLIENT();
|
||||
// ASCred storage must remain valid
|
||||
void add(dbChannel* chan, ASCred& cred);
|
||||
bool canWrite();
|
||||
};
|
||||
|
||||
struct pdbRecordInfo {
|
||||
@@ -100,11 +141,22 @@ struct pdbRecordIterator {
|
||||
}
|
||||
pdbRecordIterator(const dbChannel *chan)
|
||||
{
|
||||
#if EPICS_VERSION_INT>=VERSION_INT(3,16,1,0)
|
||||
dbInitEntryFromRecord(dbChannelRecord(chan), &ent);
|
||||
#else
|
||||
dbInitEntry(pdbbase, &ent);
|
||||
if(dbFindRecord(&ent, dbChannelRecord(chan)->name)!=0)
|
||||
throw std::runtime_error("Record not found");
|
||||
throw std::logic_error("Record not found");
|
||||
#endif
|
||||
m_done = false;
|
||||
}
|
||||
#if EPICS_VERSION_INT>=VERSION_INT(3,16,1,0)
|
||||
pdbRecordIterator(dbCommon *prec)
|
||||
{
|
||||
dbInitEntryFromRecord(prec, &ent);
|
||||
m_done = false;
|
||||
}
|
||||
#endif
|
||||
~pdbRecordIterator()
|
||||
{
|
||||
dbFinishEntry(&ent);
|
||||
@@ -128,7 +180,7 @@ struct pdbRecordIterator {
|
||||
return m_done ? NULL : (dbCommon*)ent.precnode->precord;
|
||||
}
|
||||
const char *name() const {
|
||||
return m_done ? NULL : ((dbCommon*)ent.precnode->precord)->name;
|
||||
return m_done ? NULL : ent.precnode->recordname;
|
||||
}
|
||||
const char *info(const char *key, const char *def =0)
|
||||
{
|
||||
@@ -225,8 +277,8 @@ struct DBManyLock
|
||||
{
|
||||
dbLocker *plock;
|
||||
DBManyLock() :plock(NULL) {}
|
||||
DBManyLock(const std::vector<dbCommon*>& recs, unsigned flags)
|
||||
:plock(dbLockerAlloc((dbCommon**)&recs[0], recs.size(), flags))
|
||||
DBManyLock(const std::vector<dbCommon*>& recs, unsigned flags=0)
|
||||
:plock(dbLockerAlloc( (recs.size() > 0 ? (dbCommon**)&recs[0] : NULL), recs.size(), flags))
|
||||
{
|
||||
if(!plock) throw std::invalid_argument("Failed to create locker");
|
||||
}
|
||||
@@ -257,7 +309,7 @@ struct DBManyLocker
|
||||
};
|
||||
#endif
|
||||
|
||||
struct epicsShareClass FieldName
|
||||
struct QSRV_API FieldName
|
||||
{
|
||||
struct Component {
|
||||
std::string name;
|
||||
@@ -300,7 +352,7 @@ private:
|
||||
FieldName& operator=(const FieldName&);
|
||||
};
|
||||
|
||||
struct epicsShareClass PVIF {
|
||||
struct QSRV_API PVIF {
|
||||
PVIF(dbChannel *ch);
|
||||
virtual ~PVIF() {}
|
||||
|
||||
@@ -315,9 +367,9 @@ struct epicsShareClass PVIF {
|
||||
//! Copy from PDB record to pvalue (call dbChannelGet())
|
||||
//! caller must lock record
|
||||
virtual void put(epics::pvData::BitSet& mask, unsigned dbe, db_field_log *pfl) =0;
|
||||
//! Copy from pvalue to PDB record (call dbChannelPut())
|
||||
//! May copy from pvalue to PDB record (call dbChannelPut())
|
||||
//! caller must lock record
|
||||
virtual epics::pvData::Status get(const epics::pvData::BitSet& mask, proc_t proc=ProcInhibit) =0;
|
||||
virtual epics::pvData::Status get(const epics::pvData::BitSet& mask, proc_t proc=ProcInhibit, bool permit=true) =0;
|
||||
//! Calculate DBE mask from changed bitset
|
||||
virtual unsigned dbe(const epics::pvData::BitSet& mask) =0;
|
||||
|
||||
@@ -326,7 +378,19 @@ private:
|
||||
PVIF& operator=(const PVIF&);
|
||||
};
|
||||
|
||||
struct epicsShareClass 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 {
|
||||
|
||||
virtual ~PVIFBuilder() {}
|
||||
|
||||
@@ -342,6 +406,7 @@ struct epicsShareClass PVIFBuilder {
|
||||
// must be the root structure
|
||||
virtual PVIF* attach(dbChannel *channel, const epics::pvData::PVStructurePtr& root, const FieldName& fld) =0;
|
||||
|
||||
// entry point for Builder
|
||||
static PVIFBuilder* create(const std::string& name);
|
||||
protected:
|
||||
PVIFBuilder() {}
|
||||
@@ -350,7 +415,7 @@ private:
|
||||
PVIFBuilder& operator=(const PVIFBuilder&);
|
||||
};
|
||||
|
||||
struct epicsShareClass ScalarBuilder : public PVIFBuilder
|
||||
struct QSRV_API ScalarBuilder : public PVIFBuilder
|
||||
{
|
||||
virtual ~ScalarBuilder() {}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Changes to this file may require incrementing
|
||||
# the ABI version in CONFIG_QSRV_VERSION
|
||||
|
||||
registrar(QSRVRegistrar)
|
||||
registrar(installPVAAddLinkHook)
|
||||
link("pva", "lsetPVA")
|
||||
|
||||
# from demo.cpp
|
||||
device(waveform, CONSTANT, devWfPDBDemo, "QSRV Demo")
|
||||
# from imagedemo.c
|
||||
function(QSRV_image_demo)
|
||||
# from pdb.cpp
|
||||
# Extra debug info when parsing group definitions
|
||||
variable(PDBProviderDebug, int)
|
||||
# Number of worker threads for handling monitor updates.
|
||||
# Default: 1
|
||||
variable(pvaLinkNWorkers, int)
|
||||
@@ -2,16 +2,15 @@
|
||||
# the ABI version in CONFIG_QSRV_VERSION
|
||||
|
||||
registrar(QSRVRegistrar)
|
||||
#registrar(installPVAAddLinkHook)
|
||||
#link("pva", "lsetPVA")
|
||||
registrar(installPVAAddLinkHook)
|
||||
|
||||
# from demo.cpp
|
||||
device(waveform, CONSTANT, devWfPDBDemo, "QSRV Demo")
|
||||
# from imagedemo.c
|
||||
function(QSRV_image_demo)
|
||||
# from pvif.cpp
|
||||
# Disable mapping of display.format
|
||||
variable(qsrvDisableFormat, int)
|
||||
# from pdb.cpp
|
||||
# Extra debug info when parsing group definitions
|
||||
variable(PDBProviderDebug, int)
|
||||
# Number of worker threads for handling monitor updates.
|
||||
# Default: 1
|
||||
variable(pvaLinkNWorkers, int)
|
||||
+14
-3
@@ -1,5 +1,4 @@
|
||||
|
||||
#include <epicsExport.h>
|
||||
#include <initHooks.h>
|
||||
#include <epicsExit.h>
|
||||
#include <epicsThread.h>
|
||||
@@ -12,14 +11,14 @@
|
||||
#include <dbLock.h>
|
||||
#include <dbEvent.h>
|
||||
#include <epicsVersion.h>
|
||||
#include <dbNotify.h>
|
||||
|
||||
#include <pv/reftrack.h>
|
||||
#include <pv/pvAccess.h>
|
||||
#include <pv/serverContext.h>
|
||||
#include <pv/iocshelper.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
|
||||
#include "pv/qsrv.h"
|
||||
#include "pvahelper.h"
|
||||
#include "pvif.h"
|
||||
#include "pdb.h"
|
||||
@@ -28,6 +27,8 @@
|
||||
# include "pdbgroup.h"
|
||||
#endif
|
||||
|
||||
#include <epicsExport.h>
|
||||
|
||||
namespace pva = epics::pvAccess;
|
||||
|
||||
void QSRVRegistrar_counters()
|
||||
@@ -91,6 +92,16 @@ void QSRVRegistrar()
|
||||
|
||||
} // namespace
|
||||
|
||||
unsigned qsrvVersion(void)
|
||||
{
|
||||
return QSRV_VERSION_INT;
|
||||
}
|
||||
|
||||
unsigned qsrvABIVersion(void)
|
||||
{
|
||||
return QSRV_ABI_VERSION_INT;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
epicsExportRegistrar(QSRVRegistrar);
|
||||
}
|
||||
|
||||
+219
-204
@@ -7,237 +7,252 @@
|
||||
* 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 */
|
||||
|
||||
/* Usage:
|
||||
* softIoc [-D softIoc.dbd] [-h] [-S] [-s] [-a ascf]
|
||||
* [-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 <iostream>
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <epicsVersion.h>
|
||||
#include <epicsGetopt.h>
|
||||
#include "registryFunction.h"
|
||||
#include "epicsThread.h"
|
||||
#include "epicsExit.h"
|
||||
#include "epicsStdio.h"
|
||||
#include "epicsString.h"
|
||||
#include "dbStaticLib.h"
|
||||
#include "subRecord.h"
|
||||
#include "dbAccess.h"
|
||||
#include "asDbLib.h"
|
||||
#include "iocInit.h"
|
||||
#include "iocsh.h"
|
||||
#include "osiFileName.h"
|
||||
|
||||
extern "C" int softIocPVA_registerRecordDeviceDriver(struct dbBase *pdbbase);
|
||||
|
||||
#define DBD_FILE FINAL_LOCATION "/dbd/softIocPVA.dbd"
|
||||
#define EXIT_FILE FINAL_LOCATION "/db/softIocExit.db"
|
||||
|
||||
#ifdef VERSION_INT
|
||||
#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1)
|
||||
#define USE_EXIT_LATER
|
||||
#ifndef EPICS_BASE
|
||||
// so IDEs knows EPICS_BASE is a string constant
|
||||
# define EPICS_BASE "/"
|
||||
# error -DEPICS_BASE required
|
||||
#endif
|
||||
#endif
|
||||
const char *arg0;
|
||||
const char *base_dbd = DBD_FILE;
|
||||
const char *exit_db = EXIT_FILE;
|
||||
|
||||
#if EPICS_VERSION_INT>=VERSION_INT(7,0,2,0)
|
||||
# define USE_EXECDIR
|
||||
#endif
|
||||
|
||||
#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 {
|
||||
|
||||
static void exitSubroutine(subRecord *precord) {
|
||||
#ifdef USE_EXIT_LATER
|
||||
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) {
|
||||
printf("Usage: %s [-D softIoc.dbd] [-h] [-S] [-a ascf]\n", arg0);
|
||||
puts("\t[-m macro=value,macro2=value2] [-d file.db]");
|
||||
puts("\t[-x prefix] [st.cmd]");
|
||||
puts("Compiled-in path to softIocPVA.dbd is:");
|
||||
printf("\t%s\n", base_dbd);
|
||||
epicsExit(status);
|
||||
void usage(const char *arg0, const std::string& base_dbd) {
|
||||
std::cout<<"Usage: "<<arg0<<
|
||||
" [-D softIocPVA.dbd] [-h] [-S] [-s] [-a ascf]\n"
|
||||
"[-m macro=value,macro2=value2] [-d file.db]\n"
|
||||
"[-x prefix] [st.cmd]\n"
|
||||
"\n"
|
||||
" -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"
|
||||
" -a <acf> Access Security configuration file. Macro substitution is\n"
|
||||
" performed.\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;
|
||||
|
||||
errIf(dbLoadDatabase(dbd_file.c_str(), NULL, NULL),
|
||||
std::string("Failed to load DBD file: ")+dbd_file);
|
||||
std::cout<<"dbLoadDatabase(\""<<dbd_file<<"\")\n";
|
||||
|
||||
softIocPVA_registerRecordDeviceDriver(pdbbase);
|
||||
std::cout<<"softIocPVA_registerRecordDeviceDriver(pdbbase)\n";
|
||||
registryFunctionAdd("exit", (REGISTRYFUNCTION) exitSubroutine);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char *dbd_file = const_cast<char*>(base_dbd);
|
||||
char *macros = NULL;
|
||||
char xmacro[PVNAME_STRINGSZ + 4];
|
||||
int startIocsh = 1; /* default = start shell */
|
||||
int loadedDb = 0;
|
||||
|
||||
arg0 = strrchr(*argv, '/');
|
||||
if (!arg0) {
|
||||
arg0 = *argv;
|
||||
} else {
|
||||
++arg0; /* skip the '/' */
|
||||
}
|
||||
|
||||
--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);
|
||||
try {
|
||||
std::string dbd_file(DBD_FILE),
|
||||
exit_file(EXIT_FILE),
|
||||
macros, // scratch space for macros (may be given more than once)
|
||||
xmacro;
|
||||
bool interactive = true;
|
||||
bool loadedDb = false;
|
||||
|
||||
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;
|
||||
#ifdef USE_EXECDIR
|
||||
// attempt to compute relative paths
|
||||
{
|
||||
std::string prefix;
|
||||
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:")) != -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(asSetSubstitutions(macros.c_str()))
|
||||
throw std::bad_alloc();
|
||||
std::cout<<"asSetSubstitutions(\""<<macros<<"\")\n";
|
||||
}
|
||||
if(asSetFilename(optarg))
|
||||
throw std::bad_alloc();
|
||||
std::cout<<"asSetFilename(\""<<optarg<<"\")\n";
|
||||
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);
|
||||
errIf(dbLoadRecords(optarg, macros.c_str()),
|
||||
std::string("Failed to load: ")+optarg);
|
||||
std::cout<<"dbLoadRecords(\""<<optarg<<"\"";
|
||||
if(!macros.empty())
|
||||
std::cout<<", \""<<macros<<"\"";
|
||||
std::cout<<")\n";
|
||||
loadedDb = true;
|
||||
break;
|
||||
case 'm':
|
||||
macros = optarg;
|
||||
break;
|
||||
case 'S':
|
||||
interactive = false;
|
||||
break;
|
||||
case 's':
|
||||
break; // historical
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
lazy_dbd(dbd_file);
|
||||
|
||||
if(optind<argc) {
|
||||
// run script
|
||||
// ignore any extra positional args (historical)
|
||||
|
||||
std::cout<<"# Begin "<<argv[optind]<<"\n";
|
||||
errIf(iocsh(argv[optind]),
|
||||
std::string("Error in ")+argv[optind]);
|
||||
std::cout<<"# End "<<argv[optind]<<"\n";
|
||||
|
||||
epicsThreadSleep(0.2);
|
||||
loadedDb = true; /* Give it the benefit of the doubt... */
|
||||
}
|
||||
|
||||
if (loadedDb) {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
|
||||
#include <typeinfo>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <epicsEvent.h>
|
||||
#include <epicsGuard.h>
|
||||
#include <epicsThread.h>
|
||||
#include <errlog.h>
|
||||
|
||||
#include <pv/sharedPtr.h>
|
||||
|
||||
#include "helper.h"
|
||||
#include "tpool.h"
|
||||
|
||||
typedef epicsGuard<epicsMutex> Guard;
|
||||
typedef epicsGuardRelease<epicsMutex> UnGuard;
|
||||
|
||||
WorkQueue::WorkQueue(const std::string& name)
|
||||
:name(name)
|
||||
,state(Idle)
|
||||
{}
|
||||
|
||||
WorkQueue::~WorkQueue() { close(); }
|
||||
|
||||
void WorkQueue::start(unsigned nworkers, unsigned prio)
|
||||
{
|
||||
Guard G(mutex);
|
||||
|
||||
if(state!=Idle)
|
||||
throw std::logic_error("Already started");
|
||||
|
||||
try {
|
||||
state = Active;
|
||||
|
||||
for(unsigned i=0; i<nworkers; i++) {
|
||||
p2p::auto_ptr<epicsThread> worker(new epicsThread(*this, name.c_str(),
|
||||
epicsThreadGetStackSize(epicsThreadStackSmall),
|
||||
prio));
|
||||
|
||||
worker->start();
|
||||
|
||||
workers.push_back(worker.get());
|
||||
worker.release();
|
||||
}
|
||||
}catch(...){
|
||||
UnGuard U(G); // unlock as close() blocks to join any workers which were started
|
||||
close();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void WorkQueue::close()
|
||||
{
|
||||
workers_t temp;
|
||||
|
||||
{
|
||||
Guard G(mutex);
|
||||
if(state!=Active)
|
||||
return;
|
||||
|
||||
temp.swap(workers);
|
||||
state = Stopping;
|
||||
}
|
||||
|
||||
wakeup.signal();
|
||||
|
||||
for(workers_t::iterator it(temp.begin()), end(temp.end()); it!=end; ++it)
|
||||
{
|
||||
(*it)->exitWait();
|
||||
delete *it;
|
||||
}
|
||||
|
||||
{
|
||||
Guard G(mutex);
|
||||
state = Idle;
|
||||
}
|
||||
}
|
||||
|
||||
void WorkQueue::add(const value_type& work)
|
||||
{
|
||||
bool empty;
|
||||
|
||||
{
|
||||
Guard G(mutex);
|
||||
if(state!=Active)
|
||||
return;
|
||||
|
||||
empty = queue.empty();
|
||||
|
||||
queue.push_back(work);
|
||||
}
|
||||
|
||||
if(empty) {
|
||||
wakeup.signal();
|
||||
}
|
||||
}
|
||||
|
||||
void WorkQueue::run()
|
||||
{
|
||||
Guard G(mutex);
|
||||
|
||||
std::tr1::shared_ptr<epicsThreadRunable> work;
|
||||
|
||||
while(state==Active) {
|
||||
|
||||
if(!queue.empty()) {
|
||||
work = queue.front().lock();
|
||||
queue.pop_front();
|
||||
}
|
||||
|
||||
bool last = queue.empty();
|
||||
|
||||
{
|
||||
UnGuard U(G);
|
||||
|
||||
if(work) {
|
||||
try {
|
||||
work->run();
|
||||
work.reset();
|
||||
}catch(std::exception& e){
|
||||
errlogPrintf("%s Unhandled exception from %s: %s\n",
|
||||
name.c_str(), typeid(work.get()).name(), e.what());
|
||||
work.reset();
|
||||
}
|
||||
}
|
||||
|
||||
if(last) {
|
||||
wakeup.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pass along the close() signal to next worker
|
||||
wakeup.signal();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#ifndef TPOOL_H
|
||||
#define TPOOL_H
|
||||
|
||||
#include <stdexcept>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <errlog.h>
|
||||
#include <epicsThread.h>
|
||||
#include <epicsMutex.h>
|
||||
#include <epicsEvent.h>
|
||||
|
||||
#include <pv/sharedPtr.h>
|
||||
|
||||
struct WorkQueue : private epicsThreadRunable
|
||||
{
|
||||
typedef std::tr1::weak_ptr<epicsThreadRunable> value_type;
|
||||
|
||||
private:
|
||||
const std::string name;
|
||||
|
||||
epicsMutex mutex;
|
||||
|
||||
enum state_t {
|
||||
Idle,
|
||||
Active,
|
||||
Stopping,
|
||||
} state;
|
||||
|
||||
typedef std::deque<value_type> queue_t;
|
||||
queue_t queue;
|
||||
|
||||
epicsEvent wakeup;
|
||||
|
||||
typedef std::vector<epicsThread*> workers_t;
|
||||
workers_t workers;
|
||||
|
||||
public:
|
||||
WorkQueue(const std::string& name);
|
||||
virtual ~WorkQueue();
|
||||
|
||||
void start(unsigned nworkers=1, unsigned prio = epicsThreadPriorityLow);
|
||||
void close();
|
||||
|
||||
void add(const value_type& work);
|
||||
|
||||
private:
|
||||
virtual void run();
|
||||
};
|
||||
|
||||
#endif // TPOOL_H
|
||||
+18
-7
@@ -14,6 +14,10 @@ p2pTestIoc_DBD += base.dbd
|
||||
# Tests explicitly create/destory PDB provider
|
||||
#p2pTestIoc_DBD += qsrv.dbd
|
||||
|
||||
TARGETS += $(COMMON_DIR)/pvaLinkTestIoc.dbd
|
||||
pvaLinkTestIoc_DBD += base.dbd
|
||||
pvaLinkTestIoc_DBD += qsrv.dbd
|
||||
|
||||
PROD_SRCS += utilitiesx.cpp
|
||||
PROD_LIBS += pvAccess pvData
|
||||
PROD_LIBS += $(EPICS_BASE_IOC_LIBS)
|
||||
@@ -40,20 +44,27 @@ testpdb_SRCS += p2pTestIoc_registerRecordDeviceDriver.cpp
|
||||
testpdb_LIBS += qsrv
|
||||
TESTS += testpdb
|
||||
|
||||
PROD_HOST += check_consist
|
||||
TESTPROD_HOST += check_consist
|
||||
check_consist_SRCS += check_consist.cpp
|
||||
|
||||
#TESTPROD_HOST += testpvalink
|
||||
testpvalink_SRCS += testpvalink.cpp
|
||||
testpvalink_SRCS += p2pTestIoc_registerRecordDeviceDriver.cpp
|
||||
testpvalink_LIBS += qsrv
|
||||
#TESTS += testpvalink
|
||||
|
||||
ifdef BASE_3_16
|
||||
TESTPROD_HOST += testpvalink
|
||||
testpvalink_SRCS += testpvalink.cpp
|
||||
testpvalink_SRCS += pvaLinkTestIoc_registerRecordDeviceDriver.cpp
|
||||
testpvalink_LIBS += qsrv
|
||||
TESTS += testpvalink
|
||||
|
||||
TESTPROD_HOST += testgroupconfig
|
||||
testgroupconfig_SRCS += testgroupconfig
|
||||
testgroupconfig_LIBS += qsrv pvAccess pvData
|
||||
testgroupconfig_LIBS += $(EPICS_BASE_IOC_LIBS)
|
||||
TESTS += testgroupconfig
|
||||
|
||||
TESTPROD_HOST += testdbf_copy
|
||||
testdbf_copy_SRCS += testdbf_copy
|
||||
testdbf_copy_LIBS += qsrv pvAccess pvData
|
||||
testdbf_copy_LIBS += $(EPICS_BASE_IOC_LIBS)
|
||||
TESTS += testdbf_copy
|
||||
endif
|
||||
|
||||
TESTSCRIPTS_HOST += $(TESTS:%=%.t)
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#include <pv/pvUnitTest.h>
|
||||
#include <testMain.h>
|
||||
|
||||
#include <dbStaticLib.h>
|
||||
#include <epicsTypes.h>
|
||||
#include <pv/valueBuilder.h>
|
||||
#include <pv/pvData.h>
|
||||
|
||||
|
||||
#include "pvif.h"
|
||||
|
||||
namespace pvd = epics::pvData;
|
||||
|
||||
namespace {
|
||||
template<pvd::ScalarType ENUM, typename DBF, typename E>
|
||||
void testPVD2DBR_scalar(unsigned dbf, typename pvd::meta::arg_type<typename pvd::ScalarTypeTraits<ENUM>::type>::type V, E expect)
|
||||
{
|
||||
testDiag("testPVD2DBR_scalar(%s, %s)", pvd::ScalarTypeFunc::name(ENUM), dbGetFieldTypeString(dbf));
|
||||
|
||||
pvd::PVStructure::shared_pointer top(pvd::ValueBuilder()
|
||||
.add<ENUM>("value", V)
|
||||
.buildPVStructure());
|
||||
|
||||
DBF buf;
|
||||
|
||||
copyPVD2DBF(top->getSubFieldT("value"), &buf, dbf, NULL);
|
||||
|
||||
testEqual(buf, expect);
|
||||
}
|
||||
|
||||
void testPVD2DBR_enum()
|
||||
{
|
||||
testDiag("testPVD2DBR_enum()");
|
||||
|
||||
pvd::shared_vector<std::string> choices(3);
|
||||
choices[0] = "zero";
|
||||
choices[1] = "one";
|
||||
choices[2] = "two";
|
||||
|
||||
pvd::PVStructure::shared_pointer top(pvd::ValueBuilder()
|
||||
.addNested("value")
|
||||
.add<pvd::pvInt>("index", 1)
|
||||
.add("choices", pvd::static_shared_vector_cast<const void>(pvd::freeze(choices)))
|
||||
.endNested()
|
||||
.buildPVStructure());
|
||||
|
||||
{
|
||||
epicsEnum16 ival;
|
||||
|
||||
copyPVD2DBF(top->getSubFieldT("value"), &ival, DBF_ENUM, NULL);
|
||||
testEqual(ival, 1);
|
||||
|
||||
ival = 0;
|
||||
|
||||
testOk1(!!top->getSubField("value"));
|
||||
copyPVD2DBF(top->getSubFieldT("value"), &ival, DBF_USHORT, NULL);
|
||||
testEqual(ival, 1);
|
||||
}
|
||||
|
||||
{
|
||||
epicsUInt32 ival;
|
||||
|
||||
copyPVD2DBF(top->getSubFieldT("value"), &ival, DBF_LONG, NULL);
|
||||
testEqual(ival, 1u);
|
||||
}
|
||||
|
||||
char sval[MAX_STRING_SIZE];
|
||||
|
||||
copyPVD2DBF(top->getSubFieldT("value"), sval, DBF_STRING, NULL);
|
||||
testEqual(std::string(sval) , "one");
|
||||
}
|
||||
|
||||
void testPVD2DBR_array()
|
||||
{
|
||||
testDiag("testPVD2DBR_array()");
|
||||
|
||||
pvd::shared_vector<const pvd::uint32> arr;
|
||||
{
|
||||
pvd::shared_vector<pvd::uint32> iarr(3);
|
||||
iarr[0] = 1; iarr[1] = 2; iarr[2] = 3;
|
||||
arr = pvd::freeze(iarr);
|
||||
}
|
||||
|
||||
pvd::PVStructure::shared_pointer top(pvd::ValueBuilder()
|
||||
.add("value", pvd::static_shared_vector_cast<const void>(arr))
|
||||
.buildPVStructure());
|
||||
|
||||
{
|
||||
epicsUInt16 sarr[5];
|
||||
|
||||
{
|
||||
long nreq = 5;
|
||||
copyPVD2DBF(top->getSubFieldT("value"), sarr, DBF_SHORT, &nreq);
|
||||
testEqual(nreq, 3);
|
||||
}
|
||||
|
||||
testEqual(sarr[0], arr[0]);
|
||||
testEqual(sarr[1], arr[1]);
|
||||
testEqual(sarr[2], arr[2]);
|
||||
}
|
||||
|
||||
{
|
||||
char sarr[MAX_STRING_SIZE*5];
|
||||
|
||||
{
|
||||
long nreq = 5;
|
||||
copyPVD2DBF(top->getSubFieldT("value"), sarr, DBF_STRING, &nreq);
|
||||
testEqual(nreq, 3);
|
||||
}
|
||||
|
||||
testEqual(sarr[0*MAX_STRING_SIZE+0], '1');
|
||||
testEqual(int(sarr[0*MAX_STRING_SIZE+1]), int('\0'));
|
||||
testEqual(sarr[1*MAX_STRING_SIZE+0], '2');
|
||||
testEqual(int(sarr[1*MAX_STRING_SIZE+1]), int('\0'));
|
||||
testEqual(sarr[2*MAX_STRING_SIZE+0], '3');
|
||||
testEqual(int(sarr[2*MAX_STRING_SIZE+1]), int('\0'));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename input_t, typename output_t>
|
||||
void testDBR2PVD_scalar(const input_t& input,
|
||||
const output_t& expect)
|
||||
{
|
||||
pvd::ScalarType in = (pvd::ScalarType)pvd::ScalarTypeID<input_t>::value;
|
||||
pvd::ScalarType out = (pvd::ScalarType)pvd::ScalarTypeID<output_t>::value;
|
||||
testDiag("testDBR2PVD_scalar(%s, %s)", pvd::ScalarTypeFunc::name(in), pvd::ScalarTypeFunc::name(out));
|
||||
|
||||
pvd::PVStructure::shared_pointer top(pvd::getPVDataCreate()->createPVStructure(pvd::getFieldCreate()->createFieldBuilder()
|
||||
->add("value", out) // initially zero or ""
|
||||
->createStructure()));
|
||||
pvd::PVStringArray::const_svector choices;
|
||||
pvd::BitSet changed;
|
||||
|
||||
pvd::shared_vector<input_t> buf(1);
|
||||
buf[0] = input;
|
||||
|
||||
copyDBF2PVD(pvd::static_shared_vector_cast<const void>(pvd::freeze(buf)),
|
||||
top->getSubFieldT("value"), changed, choices);
|
||||
|
||||
output_t actual = top->getSubFieldT<pvd::PVScalar>("value")->getAs<output_t>();
|
||||
testEqual(actual, expect);
|
||||
testOk1(changed.get(top->getSubFieldT("value")->getFieldOffset()));
|
||||
}
|
||||
|
||||
template<typename input_t>
|
||||
void testDBR2PVD_enum(const input_t& input, pvd::int32 expect)
|
||||
{
|
||||
testDiag("testDBR2PVD_enum()");
|
||||
|
||||
pvd::shared_vector<const std::string> choices;
|
||||
{
|
||||
pvd::shared_vector<std::string> temp(3);
|
||||
temp[0] = "zero";
|
||||
temp[1] = "one";
|
||||
temp[2] = "two";
|
||||
choices = pvd::freeze(temp);
|
||||
}
|
||||
|
||||
pvd::PVStructure::shared_pointer top(pvd::ValueBuilder()
|
||||
.addNested("value")
|
||||
.add<pvd::pvInt>("index", 0)
|
||||
.add("choices", pvd::static_shared_vector_cast<const void>(choices))
|
||||
.endNested()
|
||||
.buildPVStructure());
|
||||
|
||||
pvd::BitSet changed;
|
||||
|
||||
pvd::shared_vector<input_t> buf(1);
|
||||
buf[0] = input;
|
||||
|
||||
copyDBF2PVD(pvd::static_shared_vector_cast<const void>(pvd::freeze(buf)),
|
||||
top->getSubFieldT("value"), changed, choices);
|
||||
|
||||
pvd::int32 actual = top->getSubFieldT<pvd::PVScalar>("value.index")->getAs<pvd::int32>();
|
||||
testShow()<<top;
|
||||
testShow()<<changed;
|
||||
testEqual(actual, expect);
|
||||
testOk1(changed.get(top->getSubFieldT("value.index")->getFieldOffset()));
|
||||
}
|
||||
|
||||
void testDBR2PVD_array()
|
||||
{
|
||||
testDiag("testDBR2PVD_array()");
|
||||
|
||||
pvd::PVStructure::shared_pointer top(pvd::getPVDataCreate()->createPVStructure(pvd::getFieldCreate()->createFieldBuilder()
|
||||
->addArray("value", pvd::pvInt) // initially zero or ""
|
||||
->createStructure()));
|
||||
pvd::PVStringArray::const_svector choices;
|
||||
pvd::BitSet changed;
|
||||
|
||||
{
|
||||
pvd::shared_vector<pvd::uint32> buf(3);
|
||||
buf[0] = 1; buf[1] = 2; buf[2] = 3;
|
||||
copyDBF2PVD(pvd::static_shared_vector_cast<const void>(pvd::freeze(buf)),
|
||||
top->getSubFieldT("value"), changed, choices);
|
||||
|
||||
pvd::PVIntArray::const_svector arr(top->getSubFieldT<pvd::PVIntArray>("value")->view());
|
||||
testEqual(arr.size(), 3u);
|
||||
testEqual(arr[0], 1);
|
||||
testEqual(arr[1], 2);
|
||||
testEqual(arr[2], 3);
|
||||
testOk1(changed.get(top->getSubFieldT("value")->getFieldOffset()));
|
||||
}
|
||||
|
||||
changed.clear();
|
||||
|
||||
{
|
||||
pvd::shared_vector<std::string> buf(4);
|
||||
buf[0] = "4";
|
||||
buf[1] = "5";
|
||||
buf[2] = "6";
|
||||
buf[3] = "7";
|
||||
|
||||
copyDBF2PVD(pvd::static_shared_vector_cast<const void>(pvd::freeze(buf)),
|
||||
top->getSubFieldT("value"), changed, choices);
|
||||
|
||||
pvd::PVIntArray::const_svector arr(top->getSubFieldT<pvd::PVIntArray>("value")->view());
|
||||
testEqual(arr.size(), 4u);
|
||||
testEqual(arr[0], 4);
|
||||
testEqual(arr[1], 5);
|
||||
testEqual(arr[2], 6);
|
||||
testEqual(arr[3], 7);
|
||||
testOk1(changed.get(top->getSubFieldT("value")->getFieldOffset()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MAIN(testdbf_copy)
|
||||
{
|
||||
testPlan(53);
|
||||
try{
|
||||
testPVD2DBR_scalar<pvd::pvDouble, double>(DBF_DOUBLE, 42.2, 42.2);
|
||||
testPVD2DBR_scalar<pvd::pvDouble, pvd::uint16>(DBF_USHORT, 42.2, 42u);
|
||||
|
||||
testPVD2DBR_scalar<pvd::pvInt, pvd::int32>(DBF_LONG, 42, 42);
|
||||
testPVD2DBR_scalar<pvd::pvInt, char[MAX_STRING_SIZE]>(DBF_STRING, 42, std::string("42"));
|
||||
|
||||
testPVD2DBR_scalar<pvd::pvLong, pvd::int64>(DBF_INT64, 42, 42);
|
||||
testPVD2DBR_scalar<pvd::pvLong, char[MAX_STRING_SIZE]>(DBF_STRING, 42, std::string("42"));
|
||||
|
||||
testPVD2DBR_scalar<pvd::pvUShort, pvd::uint16>(DBF_USHORT, 41u, 41);
|
||||
testPVD2DBR_scalar<pvd::pvByte, pvd::int8>(DBF_CHAR, 41, 41);
|
||||
|
||||
testPVD2DBR_scalar<pvd::pvString, char[MAX_STRING_SIZE]>(DBF_STRING, "hello", std::string("hello"));
|
||||
testPVD2DBR_scalar<pvd::pvString, pvd::int32>(DBF_LONG, "-100", -100);
|
||||
|
||||
//testPVD2DBR_scalar<pvd::pvBoolean, pvd::int8>(DBF_CHAR, true, 1);
|
||||
|
||||
testPVD2DBR_enum();
|
||||
|
||||
testPVD2DBR_array();
|
||||
|
||||
testDBR2PVD_scalar<double, double>(42.2, 42.2);
|
||||
testDBR2PVD_scalar<pvd::uint16, double>(42u, 42.0);
|
||||
testDBR2PVD_scalar<pvd::int32, pvd::int32>(-41, -41);
|
||||
testDBR2PVD_scalar<std::string, pvd::int32>("-41", -41);
|
||||
testDBR2PVD_scalar<std::string, std::string>("hello", "hello");
|
||||
testDBR2PVD_scalar<pvd::int32, std::string>(-42, "-42");
|
||||
|
||||
testDBR2PVD_enum<pvd::uint32>(2, 2);
|
||||
testDBR2PVD_enum<std::string>("two", 2);
|
||||
|
||||
testDBR2PVD_array();
|
||||
}catch(std::exception& e){
|
||||
testFail("Unexpected exception: %s", e.what());
|
||||
}
|
||||
return testDone();
|
||||
}
|
||||
+41
-26
@@ -1,9 +1,13 @@
|
||||
|
||||
#include <dbUnitTest.h>
|
||||
#include <testMain.h>
|
||||
#include <int64inRecord.h>
|
||||
#include <int64outRecord.h>
|
||||
|
||||
#include <pv/qsrv.h>
|
||||
#include "utilities.h"
|
||||
#include "pvalink.h"
|
||||
#include "pv/qsrv.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -11,66 +15,77 @@ void testGet()
|
||||
{
|
||||
testDiag("==== testGet ====");
|
||||
|
||||
testdbGetFieldEqual("target:li.VAL", DBF_LONG, 42);
|
||||
testdbGetFieldEqual("src:li1.VAL", DBF_LONG, 0);
|
||||
testdbGetFieldEqual("src:li1.INP", DBF_STRING, "{\"pva\":\"target:li\"}");
|
||||
int64inRecord *i1 = (int64inRecord*)testdbRecordPtr("src:i1");
|
||||
|
||||
testdbPutFieldOk("src:li1.PROC", DBF_LONG, 1);
|
||||
//TODO: wait for dbEvent queue update
|
||||
epicsThreadSleep(0.1);
|
||||
while(!dbIsLinkConnected(&i1->inp))
|
||||
testqsrvWaitForLinkEvent(&i1->inp);
|
||||
|
||||
testdbGetFieldEqual("src:li1.VAL", DBF_LONG, 42);
|
||||
testdbGetFieldEqual("target:i.VAL", DBF_INT64, 42LL);
|
||||
|
||||
testdbPutFieldOk("src:li1.INP", DBF_STRING, "{\"pva\":\"target:ai\"}");
|
||||
testdbGetFieldEqual("src:i1.VAL", DBF_INT64, 0LL); // value before first process
|
||||
|
||||
testdbGetFieldEqual("src:li1.VAL", DBF_LONG, 42);
|
||||
testdbGetFieldEqual("src:i1.INP", DBF_STRING, "{\"pva\":\"target:i\"}");
|
||||
|
||||
//TODO: wait for pvalink worker update
|
||||
epicsThreadSleep(0.1);
|
||||
testdbPutFieldOk("src:li1.PROC", DBF_LONG, 1);
|
||||
//TODO: wait for dbEvent queue update
|
||||
epicsThreadSleep(0.1);
|
||||
testdbPutFieldOk("src:i1.PROC", DBF_INT64, 1LL);
|
||||
|
||||
testdbGetFieldEqual("src:li1.VAL", DBF_LONG, 4);
|
||||
testdbGetFieldEqual("src:i1.VAL", DBF_INT64, 42LL);
|
||||
|
||||
testdbPutFieldOk("src:i1.INP", DBF_STRING, "{\"pva\":\"target:ai\"}");
|
||||
|
||||
while(!dbIsLinkConnected(&i1->inp))
|
||||
testqsrvWaitForLinkEvent(&i1->inp);
|
||||
|
||||
testdbGetFieldEqual("src:i1.VAL", DBF_INT64, 42LL); // changing link doesn't automatically process
|
||||
|
||||
testdbPutFieldOk("src:i1.PROC", DBF_INT64, 1LL);
|
||||
|
||||
testdbGetFieldEqual("src:i1.VAL", DBF_INT64, 4LL); // now it's changed
|
||||
}
|
||||
|
||||
void testPut()
|
||||
{
|
||||
testDiag("==== testPut ====");
|
||||
testdbGetFieldEqual("target:li2.VAL", DBF_LONG, 43);
|
||||
testdbGetFieldEqual("src:li2.VAL", DBF_LONG, 0);
|
||||
testdbGetFieldEqual("src:li2.INP", DBF_STRING, "{\"pva\":\"target:l2\"}");
|
||||
|
||||
testdbPutFieldOk("src:li2.VAL", DBF_LONG, 14);
|
||||
int64outRecord *o2 = (int64outRecord*)testdbRecordPtr("src:o2");
|
||||
|
||||
testdbGetFieldEqual("target:li2.VAL", DBF_LONG, 14);
|
||||
testdbGetFieldEqual("src:li2.VAL", DBF_LONG, 14);
|
||||
while(!dbIsLinkConnected(&o2->out))
|
||||
testqsrvWaitForLinkEvent(&o2->out);
|
||||
|
||||
testdbGetFieldEqual("target:i2.VAL", DBF_INT64, 43LL);
|
||||
testdbGetFieldEqual("src:o2.VAL", DBF_INT64, 0LL);
|
||||
testdbGetFieldEqual("src:o2.OUT", DBF_STRING, "{\"pva\":\"target:i2\"}");
|
||||
|
||||
testdbPutFieldOk("src:o2.VAL", DBF_INT64, 14LL);
|
||||
|
||||
testdbGetFieldEqual("target:i2.VAL", DBF_INT64, 14LL);
|
||||
testdbGetFieldEqual("src:o2.VAL", DBF_INT64, 14LL);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C"
|
||||
void p2pTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
void pvaLinkTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
MAIN(testpvalink)
|
||||
{
|
||||
testPlan(0);
|
||||
testPlan(15);
|
||||
|
||||
// Disable PVA client provider, use local/QSRV provider
|
||||
pvaLinkIsolate = 1;
|
||||
pvaLinkDebug = 5;
|
||||
|
||||
try {
|
||||
TestIOC IOC;
|
||||
|
||||
testdbReadDatabase("p2pTestIoc.dbd", NULL, NULL);
|
||||
p2pTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("pvaLinkTestIoc.dbd", NULL, NULL);
|
||||
pvaLinkTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("testpvalink.db", NULL, NULL);
|
||||
|
||||
IOC.init();
|
||||
testGet();
|
||||
testPut();
|
||||
testqsrvShutdownOk();
|
||||
IOC.shutdown();
|
||||
testqsrvCleanup();
|
||||
|
||||
}catch(std::exception& e){
|
||||
testFail("Unexpected exception: %s", e.what());
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
|
||||
# used by testGet()
|
||||
record(longin, "target:li") {
|
||||
record(int64in, "target:i") {
|
||||
field(VAL, "42")
|
||||
}
|
||||
record(ai, "target:ai") {
|
||||
field(VAL, "4.0")
|
||||
}
|
||||
|
||||
record(longin, "src:li1") {
|
||||
field(INP, {pva:"target:li"})
|
||||
record(int64in, "src:i1") {
|
||||
field(INP, {pva:"target:i"})
|
||||
}
|
||||
|
||||
# used by testPut()
|
||||
record(longin, "target:li2") {
|
||||
record(int64in, "target:i2") {
|
||||
field(VAL, "43")
|
||||
}
|
||||
|
||||
record(longout, "src:li2") {
|
||||
field(OUT, {pva:"target:li"})
|
||||
record(int64out, "src:o2") {
|
||||
field(OUT, {pva:"target:i2"})
|
||||
}
|
||||
|
||||
+132
-14
@@ -7,6 +7,14 @@
|
||||
#include <aiRecord.h>
|
||||
#include <mbbiRecord.h>
|
||||
#include <stringinRecord.h>
|
||||
#include <epicsVersion.h>
|
||||
|
||||
#ifdef EPICS_VERSION_INT
|
||||
# if EPICS_VERSION_INT>=VERSION_INT(3,16,1,0)
|
||||
# define USE_INT64
|
||||
# include <int64inRecord.h>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include "helper.h"
|
||||
#include "pvif.h"
|
||||
@@ -28,11 +36,17 @@ void testScalar()
|
||||
testdbReadDatabase("p2pTestIoc.dbd", NULL, NULL);
|
||||
p2pTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("testpvif.db", NULL, NULL);
|
||||
#ifdef USE_INT64
|
||||
testdbReadDatabase("testpvif64.db", NULL, NULL);
|
||||
#endif
|
||||
|
||||
longinRecord *prec_li = (longinRecord*)testdbRecordPtr("test:li");
|
||||
stringinRecord *prec_si = (stringinRecord*)testdbRecordPtr("test:si");
|
||||
aiRecord *prec_ai = (aiRecord*)testdbRecordPtr("test:ai");
|
||||
mbbiRecord *prec_mbbi = (mbbiRecord*)testdbRecordPtr("test:mbbi");
|
||||
#ifdef USE_INT64
|
||||
int64inRecord *prec_i64 = (int64inRecord*)testdbRecordPtr("test:i64");
|
||||
#endif
|
||||
|
||||
IOC.init();
|
||||
|
||||
@@ -44,6 +58,9 @@ void testScalar()
|
||||
DBCH chan_ai("test:ai");
|
||||
DBCH chan_ai_rval("test:ai.RVAL");
|
||||
DBCH chan_mbbi("test:mbbi");
|
||||
#ifdef USE_INT64
|
||||
DBCH chan_i64("test:i64");
|
||||
#endif
|
||||
testEqual(dbChannelFieldType(chan_li), DBR_LONG);
|
||||
testEqual(dbChannelFieldType(chan_si), DBR_STRING);
|
||||
testEqual(dbChannelFieldType(chan_ai), DBR_DOUBLE);
|
||||
@@ -53,10 +70,16 @@ void testScalar()
|
||||
testEqual(dbChannelFinalFieldType(chan_ai), DBR_DOUBLE);
|
||||
testEqual(dbChannelFinalFieldType(chan_ai_rval), DBR_LONG);
|
||||
testEqual(dbChannelFinalFieldType(chan_mbbi), DBR_ENUM);
|
||||
#ifdef USE_INT64
|
||||
testEqual(dbChannelFinalFieldType(chan_i64), DBR_INT64);
|
||||
#endif
|
||||
|
||||
ScalarBuilder builder;
|
||||
|
||||
pvd::FieldConstPtr dtype_li(builder.dtype(chan_li));
|
||||
#ifdef USE_INT64
|
||||
pvd::FieldConstPtr dtype_i64(builder.dtype(chan_i64));
|
||||
#endif
|
||||
pvd::FieldConstPtr dtype_si(builder.dtype(chan_si));
|
||||
pvd::FieldConstPtr dtype_ai(builder.dtype(chan_ai));
|
||||
pvd::FieldConstPtr dtype_ai_rval(builder.dtype(chan_ai_rval));
|
||||
@@ -64,6 +87,9 @@ void testScalar()
|
||||
|
||||
pvd::StructureConstPtr dtype_root(pvd::getFieldCreate()->createFieldBuilder()
|
||||
->add("li", dtype_li)
|
||||
#ifdef USE_INT64
|
||||
->add("i64", dtype_i64)
|
||||
#endif
|
||||
->add("si", dtype_si)
|
||||
->add("ai", dtype_ai)
|
||||
->add("ai_rval", dtype_ai_rval)
|
||||
@@ -73,11 +99,16 @@ void testScalar()
|
||||
pvd::PVStructurePtr root(pvd::getPVDataCreate()->createPVStructure(dtype_root));
|
||||
|
||||
p2p::auto_ptr<PVIF> pvif_li(builder.attach(chan_li, root, FieldName("li")));
|
||||
#ifdef USE_INT64
|
||||
p2p::auto_ptr<PVIF> pvif_i64(builder.attach(chan_i64, root, FieldName("i64")));
|
||||
#endif
|
||||
p2p::auto_ptr<PVIF> pvif_si(builder.attach(chan_si, root, FieldName("si")));
|
||||
p2p::auto_ptr<PVIF> pvif_ai(builder.attach(chan_ai, root, FieldName("ai")));
|
||||
p2p::auto_ptr<PVIF> pvif_ai_rval(builder.attach(chan_ai_rval, root, FieldName("ai_rval")));
|
||||
p2p::auto_ptr<PVIF> pvif_mbbi(builder.attach(chan_mbbi, root, FieldName("mbbi")));
|
||||
|
||||
testShow()<<"Entire structure\n"<<root;
|
||||
|
||||
pvd::BitSet mask;
|
||||
|
||||
dbScanLock((dbCommon*)prec_li);
|
||||
@@ -94,21 +125,56 @@ void testScalar()
|
||||
.set(OFF("li.alarm.message"))
|
||||
.set(OFF("li.timeStamp.secondsPastEpoch"))
|
||||
.set(OFF("li.timeStamp.nanoseconds"))
|
||||
//.set(OFF("li.timeStamp.userTag"))
|
||||
.set(OFF("li.display.limitHigh"))
|
||||
.set(OFF("li.display.limitLow"))
|
||||
.set(OFF("li.display.description"))
|
||||
.set(OFF("li.display.format"))
|
||||
.set(OFF("li.display.units"))
|
||||
.set(OFF("li.display.precision"))
|
||||
.set(OFF("li.display.form.index"))
|
||||
.set(OFF("li.control.limitHigh"))
|
||||
.set(OFF("li.control.limitLow"))
|
||||
.set(OFF("li.valueAlarm.highWarningLimit"))
|
||||
.set(OFF("li.valueAlarm.lowWarningLimit"))
|
||||
.set(OFF("li.valueAlarm.highAlarmLimit"))
|
||||
.set(OFF("li.valueAlarm.lowAlarmLimit")));
|
||||
.set(OFF("li.valueAlarm.lowAlarmLimit")))
|
||||
<<" li changes\n"<<root->stream().show(mask);
|
||||
#undef OFF
|
||||
mask.clear();
|
||||
|
||||
#ifdef USE_INT64
|
||||
|
||||
dbScanLock((dbCommon*)prec_i64);
|
||||
prec_i64->time.secPastEpoch = 0x12345678;
|
||||
prec_i64->time.nsec = 12345678;
|
||||
pvif_i64->put(mask, DBE_VALUE|DBE_ALARM|DBE_PROPERTY, NULL);
|
||||
dbScanUnlock((dbCommon*)prec_i64);
|
||||
|
||||
#define OFF(NAME) (epicsUInt32)root->getSubFieldT(NAME)->getFieldOffset()
|
||||
testEqual(mask, pvd::BitSet()
|
||||
.set(OFF("i64.value"))
|
||||
.set(OFF("i64.alarm.severity"))
|
||||
.set(OFF("i64.alarm.status"))
|
||||
.set(OFF("i64.alarm.message"))
|
||||
.set(OFF("i64.timeStamp.secondsPastEpoch"))
|
||||
.set(OFF("i64.timeStamp.nanoseconds"))
|
||||
.set(OFF("i64.display.limitHigh"))
|
||||
.set(OFF("i64.display.limitLow"))
|
||||
.set(OFF("i64.display.description"))
|
||||
.set(OFF("i64.display.units"))
|
||||
.set(OFF("i64.display.precision"))
|
||||
.set(OFF("i64.display.form.index"))
|
||||
.set(OFF("i64.control.limitHigh"))
|
||||
.set(OFF("i64.control.limitLow"))
|
||||
.set(OFF("i64.valueAlarm.highWarningLimit"))
|
||||
.set(OFF("i64.valueAlarm.lowWarningLimit"))
|
||||
.set(OFF("i64.valueAlarm.highAlarmLimit"))
|
||||
.set(OFF("i64.valueAlarm.lowAlarmLimit")))
|
||||
<<" i64 changes\n"<<root->stream().show(mask);
|
||||
#undef OFF
|
||||
mask.clear();
|
||||
|
||||
#endif
|
||||
|
||||
dbScanLock((dbCommon*)prec_si);
|
||||
prec_si->time.secPastEpoch = 0x12345678;
|
||||
prec_si->time.nsec = 12345678;
|
||||
@@ -127,10 +193,12 @@ void testScalar()
|
||||
.set(OFF("si.display.limitHigh"))
|
||||
.set(OFF("si.display.limitLow"))
|
||||
.set(OFF("si.display.description"))
|
||||
.set(OFF("si.display.format"))
|
||||
.set(OFF("si.display.units"))
|
||||
.set(OFF("si.display.precision"))
|
||||
.set(OFF("si.display.form.index"))
|
||||
.set(OFF("si.control.limitHigh"))
|
||||
.set(OFF("si.control.limitLow")));
|
||||
.set(OFF("si.control.limitLow")))
|
||||
<<" si changes\n"<<root->stream().show(mask);
|
||||
#undef OFF
|
||||
mask.clear();
|
||||
|
||||
@@ -153,7 +221,8 @@ void testScalar()
|
||||
.set(OFF("ai.display.limitHigh"))
|
||||
.set(OFF("ai.display.limitLow"))
|
||||
.set(OFF("ai.display.description"))
|
||||
.set(OFF("ai.display.format"))
|
||||
.set(OFF("ai.display.precision"))
|
||||
.set(OFF("ai.display.form.index"))
|
||||
.set(OFF("ai.display.units"))
|
||||
.set(OFF("ai.control.limitHigh"))
|
||||
.set(OFF("ai.control.limitLow"))
|
||||
@@ -171,15 +240,17 @@ void testScalar()
|
||||
.set(OFF("ai_rval.display.limitHigh"))
|
||||
.set(OFF("ai_rval.display.limitLow"))
|
||||
.set(OFF("ai_rval.display.description"))
|
||||
.set(OFF("ai_rval.display.format"))
|
||||
.set(OFF("ai_rval.display.units"))
|
||||
.set(OFF("ai_rval.display.precision"))
|
||||
.set(OFF("ai_rval.display.form.index"))
|
||||
.set(OFF("ai_rval.control.limitHigh"))
|
||||
.set(OFF("ai_rval.control.limitLow"))
|
||||
.set(OFF("ai_rval.valueAlarm.highWarningLimit"))
|
||||
.set(OFF("ai_rval.valueAlarm.lowWarningLimit"))
|
||||
.set(OFF("ai_rval.valueAlarm.highAlarmLimit"))
|
||||
.set(OFF("ai_rval.valueAlarm.lowAlarmLimit"))
|
||||
);
|
||||
)
|
||||
<<" ai changes\n"<<root->stream().show(mask);
|
||||
#undef OFF
|
||||
mask.clear();
|
||||
|
||||
@@ -198,7 +269,8 @@ void testScalar()
|
||||
.set(OFF("mbbi.alarm.message"))
|
||||
.set(OFF("mbbi.timeStamp.secondsPastEpoch"))
|
||||
.set(OFF("mbbi.timeStamp.nanoseconds"))
|
||||
.set(OFF("mbbi.timeStamp.userTag")));
|
||||
.set(OFF("mbbi.timeStamp.userTag")))
|
||||
<<" mbbi changes\n"<<root->stream().show(mask);
|
||||
#undef OFF
|
||||
mask.clear();
|
||||
|
||||
@@ -210,6 +282,22 @@ void testScalar()
|
||||
testFieldEqual<pvd::PVDouble>(root, "li.display.limitHigh", 100.0);
|
||||
testFieldEqual<pvd::PVDouble>(root, "li.display.limitLow", 10.0);
|
||||
testFieldEqual<pvd::PVString>(root, "li.display.units", "arb");
|
||||
testFieldEqual<pvd::PVInt>(root, "li.display.precision", 0);
|
||||
testFieldEqual<pvd::PVInt>(root, "li.display.form.index", 4); // "Hex"
|
||||
|
||||
#ifdef USE_INT64
|
||||
testFieldEqual<pvd::PVLong>(root, "i64.value", 0x7fffffffffffffffLL);
|
||||
testFieldEqual<pvd::PVInt>(root, "i64.alarm.severity", 1);
|
||||
testFieldEqual<pvd::PVInt>(root, "i64.alarm.status", 1);
|
||||
testFieldEqual<pvd::PVLong>(root, "i64.timeStamp.secondsPastEpoch", 0x12345678+POSIX_TIME_AT_EPICS_EPOCH);
|
||||
testFieldEqual<pvd::PVInt>(root, "i64.timeStamp.nanoseconds", 12345678);
|
||||
testFieldEqual<pvd::PVDouble>(root, "i64.display.limitHigh", 100.0);
|
||||
testFieldEqual<pvd::PVDouble>(root, "i64.display.limitLow", 10.0);
|
||||
testTodoBegin("Bug in int64inRecord get_units()");
|
||||
testFieldEqual<pvd::PVString>(root, "i64.display.units", "arb");
|
||||
testTodoEnd();
|
||||
testFieldEqual<pvd::PVInt>(root, "i64.display.precision", 0);
|
||||
#endif
|
||||
|
||||
testFieldEqual<pvd::PVString>(root, "si.value", "hello");
|
||||
testFieldEqual<pvd::PVInt>(root, "si.alarm.severity", 0);
|
||||
@@ -223,8 +311,9 @@ void testScalar()
|
||||
testFieldEqual<pvd::PVInt>(root, "ai.timeStamp.nanoseconds", 12345678);
|
||||
testFieldEqual<pvd::PVDouble>(root, "ai.display.limitHigh", 200.0);
|
||||
testFieldEqual<pvd::PVDouble>(root, "ai.display.limitLow", 20.0);
|
||||
testFieldEqual<pvd::PVString>(root, "ai.display.format", "");
|
||||
testFieldEqual<pvd::PVInt>(root, "ai.display.precision", 2);
|
||||
testFieldEqual<pvd::PVString>(root, "ai.display.units", "foo");
|
||||
testFieldEqual<pvd::PVInt>(root, "ai.display.form.index", 0);
|
||||
|
||||
testFieldEqual<pvd::PVInt>(root, "ai_rval.value", 1234);
|
||||
testFieldEqual<pvd::PVInt>(root, "ai_rval.alarm.severity", 2);
|
||||
@@ -233,8 +322,9 @@ void testScalar()
|
||||
testFieldEqual<pvd::PVInt>(root, "ai_rval.timeStamp.userTag", 0);
|
||||
testFieldEqual<pvd::PVDouble>(root, "ai_rval.display.limitHigh", 2147483647.0);
|
||||
testFieldEqual<pvd::PVDouble>(root, "ai_rval.display.limitLow", -2147483648.0);
|
||||
testFieldEqual<pvd::PVString>(root, "ai_rval.display.format", "");
|
||||
testFieldEqual<pvd::PVInt>(root, "ai_rval.display.precision", 0);
|
||||
testFieldEqual<pvd::PVString>(root, "ai_rval.display.units", "");
|
||||
testFieldEqual<pvd::PVInt>(root, "ai_rval.display.form.index", 0);
|
||||
|
||||
testFieldEqual<pvd::PVInt>(root, "mbbi.value.index", 1);
|
||||
testFieldEqual<pvd::PVInt>(root, "mbbi.alarm.severity", 0);
|
||||
@@ -250,6 +340,9 @@ void testScalar()
|
||||
}
|
||||
|
||||
root->getSubFieldT<pvd::PVInt>("li.value")->put(102043);
|
||||
#ifdef USE_INT64
|
||||
root->getSubFieldT<pvd::PVLong>("i64.value")->put(-0x8000000000000000LL);
|
||||
#endif
|
||||
root->getSubFieldT<pvd::PVString>("si.value")->put("world");
|
||||
root->getSubFieldT<pvd::PVDouble>("ai.value")->put(44.4);
|
||||
root->getSubFieldT<pvd::PVInt>("ai_rval.value")->put(2143);
|
||||
@@ -262,6 +355,24 @@ void testScalar()
|
||||
testEqual(prec_li->val, 102043);
|
||||
dbScanUnlock((dbCommon*)prec_li);
|
||||
|
||||
#ifdef USE_INT64
|
||||
dbScanLock((dbCommon*)prec_i64);
|
||||
mask.clear();
|
||||
mask.set(root->getSubFieldT("i64.value")->getFieldOffset());
|
||||
pvif_i64->get(mask);
|
||||
testEqual(prec_i64->val, epicsInt64(-0x8000000000000000LL));
|
||||
dbScanUnlock((dbCommon*)prec_i64);
|
||||
#endif
|
||||
|
||||
#ifdef USE_INT64
|
||||
dbScanLock((dbCommon*)prec_i64);
|
||||
mask.clear();
|
||||
mask.set(root->getSubFieldT("i64.value")->getFieldOffset());
|
||||
pvif_i64->get(mask);
|
||||
testEqual(prec_i64->val, epicsInt64(-0x8000000000000000LL));
|
||||
dbScanUnlock((dbCommon*)prec_i64);
|
||||
#endif
|
||||
|
||||
dbScanLock((dbCommon*)prec_si);
|
||||
mask.clear();
|
||||
mask.set(root->getSubFieldT("si.value")->getFieldOffset());
|
||||
@@ -289,8 +400,6 @@ void testScalar()
|
||||
pvif_mbbi->get(mask);
|
||||
testEqual(prec_mbbi->val, 2);
|
||||
dbScanUnlock((dbCommon*)prec_mbbi);
|
||||
|
||||
iocshCmd("stopPVAServer");
|
||||
}
|
||||
|
||||
void testPlain()
|
||||
@@ -416,7 +525,16 @@ void testPlain()
|
||||
|
||||
MAIN(testpvif)
|
||||
{
|
||||
testPlan(71);
|
||||
testPlan(75
|
||||
#ifdef USE_INT64
|
||||
+13
|
||||
#endif
|
||||
);
|
||||
#ifdef USE_INT64
|
||||
testDiag("Testing of 64-bit field access");
|
||||
#else
|
||||
testDiag("64-bit field access not supported");
|
||||
#endif
|
||||
testScalar();
|
||||
testPlain();
|
||||
return testDone();
|
||||
|
||||
@@ -5,6 +5,7 @@ record(longin, "test:li") {
|
||||
field(EGU, "arb")
|
||||
field(HOPR, "100")
|
||||
field(LOPR, "10")
|
||||
info(Q:form, "Hex")
|
||||
}
|
||||
record(ai, "test:ai") {
|
||||
field(VAL, "42.2")
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
record(int64in, "test:i64") {
|
||||
field(VAL, "0x7fffffffffffffff")
|
||||
field(SEVR, "1")
|
||||
field(STAT, "1")
|
||||
field(EGU, "arb")
|
||||
field(HOPR, "100")
|
||||
field(LOPR, "10")
|
||||
}
|
||||
Reference in New Issue
Block a user