Compare commits
82 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 466d41ebb9 | |||
| d18e2219b3 | |||
| ff22538129 | |||
| 67e668795e | |||
| 1ac2e6c809 | |||
| 40b327cfcb | |||
| 20edb8fdf5 | |||
| babda345b4 | |||
| 04fbaf5a2e | |||
| 3438fde276 | |||
| b515fd30ca | |||
| ad8b77e19f | |||
| b8389ac6a1 | |||
| d47008ee53 | |||
| a709854f0d | |||
| 527afaf856 | |||
| d65af720d1 | |||
| 503cf414be | |||
| 1682c991d2 | |||
| c68c0038e6 | |||
| b69c25feb6 | |||
| eae493e732 | |||
| 29d00e7e38 | |||
| 1335d75403 | |||
| c32cc1ba3c | |||
| f5278ef4ac | |||
| b9d3ad8f90 | |||
| 0e772037ee | |||
| d100eac09e | |||
| 740aad6712 | |||
| c66f224602 | |||
| b1d28cca39 | |||
| 50be5b6025 | |||
| 8e7a1ad295 | |||
| e894ae753f | |||
| 4559f483bf | |||
| e843db5d27 | |||
| 8363c870bd | |||
| 9499137bb4 | |||
| c4798dc2bc | |||
| 11eeed9d0c | |||
| 94d1eedc75 | |||
| cb13435d15 | |||
| cda2222ed5 | |||
| 68708ff530 | |||
| 137ecfed56 | |||
| 29a6f261dc | |||
| 1d9fbbea0b | |||
| 536f4dd02f | |||
| 21ae754869 | |||
| 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 |
+103
@@ -0,0 +1,103 @@
|
||||
# .appveyor.yml for use with EPICS Base ci-scripts
|
||||
# (see: https://github.com/epics-base/ci-scripts)
|
||||
|
||||
# This is YAML - indentation levels are crucial
|
||||
|
||||
cache:
|
||||
- C:\Users\appveyor\.tools
|
||||
|
||||
#---------------------------------#
|
||||
# additional packages #
|
||||
#---------------------------------#
|
||||
|
||||
install:
|
||||
# for the sequencer
|
||||
- cinst re2c
|
||||
- cmd: git submodule update --init --recursive
|
||||
|
||||
#---------------------------------#
|
||||
# repository cloning #
|
||||
#---------------------------------#
|
||||
|
||||
init:
|
||||
# Set autocrlf to make batch files work
|
||||
- git config --global core.autocrlf true
|
||||
|
||||
clone_depth: 50
|
||||
|
||||
# Skipping commits affecting only specific files
|
||||
skip_commits:
|
||||
files:
|
||||
- 'documentation/*'
|
||||
- '**/*.md'
|
||||
- '.github/**'
|
||||
|
||||
# Build Configurations: dll/static, regular/debug
|
||||
configuration:
|
||||
- dynamic
|
||||
- static
|
||||
- dynamic-debug
|
||||
- static-debug
|
||||
|
||||
# Environment variables: compiler toolchain, base version, setup file, ...
|
||||
environment:
|
||||
# common / default variables for all jobs
|
||||
SETUP_PATH: .ci-local:.ci
|
||||
|
||||
matrix:
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
CMP: vs2019
|
||||
BASE: 7.0
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
||||
CMP: gcc
|
||||
BASE: 7.0
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
CMP: vs2017
|
||||
BASE: 7.0
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
CMP: vs2019
|
||||
BASE: 3.15
|
||||
|
||||
# Platform: processor architecture
|
||||
platform:
|
||||
- x64
|
||||
|
||||
#---------------------------------#
|
||||
# building & testing #
|
||||
#---------------------------------#
|
||||
|
||||
build_script:
|
||||
- cmd: python .ci/cue.py prepare
|
||||
- cmd: python .ci/cue.py build
|
||||
|
||||
test_script:
|
||||
- cmd: python .ci/cue.py test
|
||||
|
||||
on_finish:
|
||||
- ps: Get-ChildItem *.tap -Recurse -Force | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
|
||||
- cmd: python .ci/cue.py build test-results -s
|
||||
|
||||
#---------------------------------#
|
||||
# debugging #
|
||||
#---------------------------------#
|
||||
|
||||
## if you want to connect by remote desktop to a failed build, uncomment these lines
|
||||
## note that you will need to connect within the usual build timeout limit (60 minutes)
|
||||
## so you may want to adjust the build matrix above to just build the one of interest
|
||||
|
||||
#on_failure:
|
||||
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
|
||||
#---------------------------------#
|
||||
# notifications #
|
||||
#---------------------------------#
|
||||
|
||||
notifications:
|
||||
|
||||
- provider: Email
|
||||
to:
|
||||
- core-talk@aps.anl.gov
|
||||
on_build_success: false
|
||||
|
||||
- provider: GitHubPullRequest
|
||||
Submodule
+1
Submodule .ci added at 3db08b5977
@@ -0,0 +1,14 @@
|
||||
# EPICS Base
|
||||
BASE_DIRNAME=base
|
||||
BASE_REPONAME=epics-base
|
||||
BASE_REPOOWNER=epics-base
|
||||
BASE_VARNAME=EPICS_BASE
|
||||
BASE_RECURSIVE=NO
|
||||
|
||||
MODULES=PVDATA PVACCESS
|
||||
|
||||
PVDATA_REPONAME=pvDataCPP
|
||||
PVDATA_REPOOWNER=epics-base
|
||||
|
||||
PVACCESS_REPONAME=pvAccessCPP
|
||||
PVACCESS_REPOOWNER=epics-base
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e -x
|
||||
|
||||
make -j2 $EXTRA
|
||||
|
||||
if [ "$TEST" != "NO" ]
|
||||
then
|
||||
make tapfiles
|
||||
make -s test-results
|
||||
fi
|
||||
@@ -1,112 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e -x
|
||||
|
||||
CURDIR="$PWD"
|
||||
|
||||
cat << EOF > $CURDIR/configure/RELEASE.local
|
||||
EPICS_BASE=$HOME/.source/epics-base
|
||||
EOF
|
||||
|
||||
install -d "$HOME/.source"
|
||||
cd "$HOME/.source"
|
||||
|
||||
add_gh_flat() {
|
||||
MODULE=$1
|
||||
REPOOWNER=$2
|
||||
REPONAME=$3
|
||||
BRANCH=$4
|
||||
MODULE_UC=$(echo $MODULE | tr 'a-z' 'A-Z')
|
||||
( git clone --quiet --depth 5 --branch $BRANCH https://github.com/$REPOOWNER/$REPONAME.git $MODULE && \
|
||||
cd $MODULE && git log -n1 )
|
||||
cat < $CURDIR/configure/RELEASE.local > $MODULE/configure/RELEASE.local
|
||||
cat << EOF >> $CURDIR/configure/RELEASE.local
|
||||
${MODULE_UC}=$HOME/.source/$MODULE
|
||||
EOF
|
||||
}
|
||||
|
||||
# not recursive
|
||||
git clone --quiet --depth 5 --branch "$BRBASE" https://github.com/${REPOBASE:-epics-base}/epics-base.git epics-base
|
||||
(cd epics-base && git log -n1 )
|
||||
add_gh_flat pvData ${REPOPVD:-epics-base} pvDataCPP ${BRPVD:-master}
|
||||
add_gh_flat pvAccess ${REPOPVA:-epics-base} pvAccessCPP ${BRPVA:-master}
|
||||
|
||||
if [ -e $CURDIR/configure/RELEASE.local ]
|
||||
then
|
||||
cat $CURDIR/configure/RELEASE.local
|
||||
fi
|
||||
|
||||
EPICS_HOST_ARCH=`sh epics-base/startup/EpicsHostArch`
|
||||
|
||||
# requires wine and g++-mingw-w64-i686
|
||||
if [ "$WINE" = "32" ]
|
||||
then
|
||||
echo "Cross mingw32"
|
||||
sed -i -e '/CMPLR_PREFIX/d' epics-base/configure/os/CONFIG_SITE.linux-x86.win32-x86-mingw
|
||||
cat << EOF >> epics-base/configure/os/CONFIG_SITE.linux-x86.win32-x86-mingw
|
||||
CMPLR_PREFIX=i686-w64-mingw32-
|
||||
EOF
|
||||
cat << EOF >> epics-base/configure/CONFIG_SITE
|
||||
CROSS_COMPILER_TARGET_ARCHS+=win32-x86-mingw
|
||||
EOF
|
||||
fi
|
||||
|
||||
if [ "$STATIC" = "YES" ]
|
||||
then
|
||||
echo "Build static libraries/executables"
|
||||
cat << EOF >> epics-base/configure/CONFIG_SITE
|
||||
SHARED_LIBRARIES=NO
|
||||
STATIC_BUILD=YES
|
||||
EOF
|
||||
fi
|
||||
|
||||
case "$CMPLR" in
|
||||
clang)
|
||||
echo "Host compiler is clang"
|
||||
cat << EOF >> epics-base/configure/os/CONFIG_SITE.Common.$EPICS_HOST_ARCH
|
||||
GNU = NO
|
||||
CMPLR_CLASS = clang
|
||||
CC = clang
|
||||
CCC = clang++
|
||||
EOF
|
||||
|
||||
# hack
|
||||
sed -i -e 's/CMPLR_CLASS = gcc/CMPLR_CLASS = clang/' epics-base/configure/CONFIG.gnuCommon
|
||||
|
||||
clang --version
|
||||
;;
|
||||
*)
|
||||
echo "Host compiler is default"
|
||||
gcc --version
|
||||
;;
|
||||
esac
|
||||
|
||||
cat <<EOF >> epics-base/configure/CONFIG_SITE
|
||||
USR_CPPFLAGS += $USR_CPPFLAGS
|
||||
USR_CFLAGS += $USR_CFLAGS
|
||||
USR_CXXFLAGS += $USR_CXXFLAGS
|
||||
EOF
|
||||
|
||||
# set RTEMS to eg. "4.9" or "4.10"
|
||||
# requires qemu, bison, flex, texinfo, install-info
|
||||
if [ -n "$RTEMS" ]
|
||||
then
|
||||
echo "Cross RTEMS${RTEMS} for pc386"
|
||||
curl -L "https://github.com/mdavidsaver/rsb/releases/download/20171203-${RTEMS}/i386-rtems${RTEMS}-trusty-20171203-${RTEMS}.tar.bz2" \
|
||||
| tar -C / -xmj
|
||||
|
||||
sed -i -e '/^RTEMS_VERSION/d' -e '/^RTEMS_BASE/d' epics-base/configure/os/CONFIG_SITE.Common.RTEMS
|
||||
cat << EOF >> epics-base/configure/os/CONFIG_SITE.Common.RTEMS
|
||||
RTEMS_VERSION=$RTEMS
|
||||
RTEMS_BASE=$HOME/.rtems
|
||||
EOF
|
||||
cat << EOF >> epics-base/configure/CONFIG_SITE
|
||||
CROSS_COMPILER_TARGET_ARCHS += RTEMS-pc386-qemu
|
||||
EOF
|
||||
fi
|
||||
|
||||
make -j2 -C epics-base $EXTRA
|
||||
make -j2 -C pvData $EXTRA
|
||||
make -j2 -C pvAccess $EXTRA
|
||||
|
||||
find epics-base/include
|
||||
find epics-base/lib
|
||||
@@ -0,0 +1,179 @@
|
||||
# .github/workflows/ci-scripts-build.yml for use with EPICS Base ci-scripts
|
||||
# (see: https://github.com/epics-base/ci-scripts)
|
||||
|
||||
# This is YAML - indentation levels are crucial
|
||||
|
||||
# Workflow name
|
||||
|
||||
name: Base
|
||||
|
||||
# Trigger on pushes and PRs to any branch
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- .appveyor.yml
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
SETUP_PATH: .ci-local:.ci
|
||||
EPICS_TEST_IMPRECISE_TIMING: YES
|
||||
|
||||
jobs:
|
||||
build-base:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
# Set environment variables from matrix parameters
|
||||
env:
|
||||
BASE: ${{ matrix.base }}
|
||||
CMP: ${{ matrix.cmp }}
|
||||
BCFG: ${{ matrix.configuration }}
|
||||
WINE: ${{ matrix.wine }}
|
||||
RTEMS: ${{ matrix.rtems }}
|
||||
EXTRA: ${{ matrix.extra }}
|
||||
TEST: ${{ matrix.test }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Job names also name artifacts, character limitations apply
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
base: "7.0"
|
||||
wine: "64"
|
||||
name: "7.0 Ub-20 gcc-9 + MinGW"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: static
|
||||
base: "7.0"
|
||||
wine: "64"
|
||||
name: "7.0 Ub-20 gcc-9 + MinGW, static"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
base: "3.15"
|
||||
wine: "64"
|
||||
name: "3.15 Ub-20 gcc-9 + MinGW"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: static
|
||||
base: "7.0"
|
||||
extra: "CMD_CXXFLAGS=-std=c++11"
|
||||
name: "7.0 Ub-20 gcc-9 C++11, static"
|
||||
|
||||
- os: ubuntu-16.04
|
||||
cmp: clang
|
||||
configuration: default
|
||||
base: "7.0"
|
||||
name: "7.0 Ub-16 clang-9"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: clang
|
||||
configuration: default
|
||||
base: "7.0"
|
||||
extra: "CMD_CXXFLAGS=-std=c++11"
|
||||
name: "7.0 Ub-20 clang-10 C++11"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
base: "7.0"
|
||||
rtems: "4.10"
|
||||
name: "7.0 Ub-20 gcc-9 + RT-4.10"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
base: "7.0"
|
||||
rtems: "4.9"
|
||||
name: "7.0 Ub-20 gcc-9 + RT-4.9"
|
||||
|
||||
- os: ubuntu-16.04
|
||||
cmp: gcc-4.8
|
||||
utoolchain: "4.8"
|
||||
configuration: default
|
||||
base: "7.0"
|
||||
name: "7.0 Ub-16 gcc-4.8"
|
||||
|
||||
- os: ubuntu-16.04
|
||||
cmp: gcc-4.9
|
||||
utoolchain: "4.9"
|
||||
configuration: default
|
||||
base: "7.0"
|
||||
name: "7.0 Ub-16 gcc-4.9"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc-8
|
||||
utoolchain: "8"
|
||||
configuration: default
|
||||
base: "7.0"
|
||||
name: "7.0 Ub-20 gcc-8"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: clang
|
||||
configuration: default
|
||||
base: "7.0"
|
||||
name: "7.0 Ub-20 clang-10"
|
||||
|
||||
- os: macos-latest
|
||||
cmp: clang
|
||||
configuration: default
|
||||
base: "7.0"
|
||||
name: "7.0 MacOS clang-12"
|
||||
|
||||
- os: windows-2019
|
||||
cmp: vs2019
|
||||
configuration: default
|
||||
base: "7.0"
|
||||
name: "7.0 Win2019 MSC-19"
|
||||
|
||||
- os: windows-2019
|
||||
cmp: vs2019
|
||||
configuration: static
|
||||
base: "7.0"
|
||||
name: "7.0 Win2019 MSC-19, static"
|
||||
|
||||
- os: windows-2019
|
||||
cmp: gcc
|
||||
configuration: static
|
||||
base: "7.0"
|
||||
name: "7.0 Win2019 mingw, static"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Cache Dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache
|
||||
key: ${{ matrix.base }}/${{ matrix.os }}/${{ matrix.cmp }}/${{ matrix.configuration }}/${{ matrix.wine }}${{ matrix.rtems }}/${{ matrix.extra }}/${{ hashFiles('.github/workflows/ci-scripts-build.yml') }}
|
||||
- name: "apt-get install"
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install qemu-system-x86 g++-mingw-w64-x86-64 gdb
|
||||
if: runner.os == 'Linux'
|
||||
- name: "apt-get install gcc-${{ matrix.utoolchain }}"
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install software-properties-common
|
||||
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install g++-${{ matrix.utoolchain }}
|
||||
if: matrix.utoolchain
|
||||
- name: Prepare and compile dependencies
|
||||
run: python .ci/cue.py prepare
|
||||
- name: Build main module
|
||||
run: python .ci/cue.py build
|
||||
- name: Run main module tests
|
||||
run: python .ci/cue.py test
|
||||
- name: Upload tapfiles Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: tapfiles ${{ matrix.name }}
|
||||
path: '**/O.*/*.tap'
|
||||
- name: Collect and show test results
|
||||
run: python .ci/cue.py test-results
|
||||
@@ -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
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
sudo: false
|
||||
dist: trusty
|
||||
language: c++
|
||||
compiler:
|
||||
- gcc
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libreadline6-dev
|
||||
- libncurses5-dev
|
||||
- perl
|
||||
- clang
|
||||
- g++-mingw-w64-i686
|
||||
- qemu-system-x86
|
||||
install:
|
||||
- ./.ci/travis-prepare.sh
|
||||
script:
|
||||
- ./.ci/travis-build.sh
|
||||
env:
|
||||
- BRBASE=7.0
|
||||
- BRBASE=7.0 CMPLR=clang
|
||||
- BRBASE=7.0 EXTRA="CMD_CXXFLAGS=-std=c++98"
|
||||
- BRBASE=7.0 EXTRA="CMD_CXXFLAGS=-std=c++11"
|
||||
- BRBASE=7.0 CMPLR=clang EXTRA="CMD_CXXFLAGS=-std=c++11"
|
||||
- BRBASE=7.0 WINE=32 TEST=NO STATIC=YES
|
||||
- BRBASE=7.0 WINE=32 TEST=NO STATIC=NO
|
||||
- BRBASE=7.0 RTEMS=4.10 TEST=NO
|
||||
- BRBASE=7.0 RTEMS=4.9 TEST=NO
|
||||
- BRBASE=3.16
|
||||
- BRBASE=3.15
|
||||
@@ -21,6 +21,9 @@ p2p
|
||||
A PV Access gateway (aka proxy).
|
||||
The 'p2p' executable.
|
||||
|
||||
The P2P gateway has been deprecated in favor of
|
||||
[p4p.gw](https://mdavidsaver.github.io/p4p/gw.html).
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
@@ -109,9 +112,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 +122,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
|
||||
```
|
||||
|
||||
@@ -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 = 1
|
||||
EPICS_QSRV_MINOR_VERSION = 3
|
||||
EPICS_QSRV_MAINTENANCE_VERSION = 1
|
||||
EPICS_QSRV_DEVELOPMENT_FLAG = 1
|
||||
|
||||
# ABI version
|
||||
EPICS_QSRV_ABI_MAJOR_VERSION = 1
|
||||
EPICS_QSRV_ABI_MINOR_VERSION = 1
|
||||
EPICS_QSRV_ABI_MINOR_VERSION = 2
|
||||
|
||||
# Development flag, set to zero for release versions
|
||||
|
||||
EPICS_QSRV_DEVELOPMENT_FLAG = 0
|
||||
|
||||
# Immediately after a release the MAINTENANCE_VERSION
|
||||
# will be incremented and the DEVELOPMENT_FLAG set to 1
|
||||
|
||||
@@ -38,7 +38,7 @@ PROJECT_NAME = pva2pva
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 1.0.1-DEV
|
||||
PROJECT_NUMBER = 1.3.1
|
||||
|
||||
# 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>
|
||||
@@ -11,7 +11,8 @@ 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
|
||||
Documentation of @ref qsrv_config including @ref qsrv_group_def ,
|
||||
@ref qsrv_aslib
|
||||
and @ref qsrv_link configuration.
|
||||
|
||||
- @ref release_notes
|
||||
|
||||
@@ -23,7 +23,9 @@ which is also the PV name.
|
||||
So unlike records, the "field" of a group have a different meaning.
|
||||
Group field names are _not_ part of the PV name.
|
||||
|
||||
A group definition is split among several records.
|
||||
A group definition may be split among several records,
|
||||
or included in separate JSON file(s).
|
||||
|
||||
For example of a group including two records is:
|
||||
|
||||
@code
|
||||
@@ -43,6 +45,23 @@ record(ai, "rec:Y") {
|
||||
}
|
||||
@endcode
|
||||
|
||||
Or equivalently with separate .db file and .json files.
|
||||
|
||||
@code
|
||||
# some .db
|
||||
record(ai, "rec:X") {}
|
||||
record(ai, "rec:Y") {}
|
||||
@endcode
|
||||
|
||||
@code
|
||||
{
|
||||
"grp:name": {
|
||||
"X": {+channel:"VAL"},
|
||||
"Y": {+channel:"VAL"}
|
||||
}
|
||||
}
|
||||
@endcode
|
||||
|
||||
This group, named "grp:name", has two fields "X" and "Y".
|
||||
|
||||
@code
|
||||
@@ -141,6 +160,54 @@ record(ai, "...") {
|
||||
}
|
||||
@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,
|
||||
@@ -149,8 +216,6 @@ for PVA links is quite different.
|
||||
|
||||
@note The "dbjlr" and "dbpvar" IOC shell command provide information about PVA links in a running IOC.
|
||||
|
||||
@warning The PVA Link syntax shown below is provisional and subject to change.
|
||||
|
||||
A simple configuration using defaults is
|
||||
|
||||
@code
|
||||
|
||||
@@ -2,21 +2,76 @@
|
||||
|
||||
@page release_notes Release Notes
|
||||
|
||||
Release 1.3.1 (June 2021)
|
||||
=========================
|
||||
|
||||
- Bug Fixes
|
||||
- Correct handling for server side filters.
|
||||
- Changes
|
||||
- Syncing softMain.cpp with epics-base
|
||||
|
||||
Release 1.3.0 (Feb 2021)
|
||||
========================
|
||||
|
||||
- Changes
|
||||
- Add dbLoadGroup() iocsh function to read group JSON definitions
|
||||
from a file. Mappings in files must refer to full record names
|
||||
instead of fields. eg. 'recname.VAL' instead of 'VAL'.
|
||||
|
||||
Release 1.2.4 (July 2020)
|
||||
=========================
|
||||
|
||||
- Bug Fixes
|
||||
- Fix stalled monitor when server side filter drops initial DBE_PROPERTY update.
|
||||
- Respect DISP
|
||||
- Changes
|
||||
- Refreshed softIocPVA to match options in Base.
|
||||
|
||||
Release 1.2.3 (May 2020)
|
||||
========================
|
||||
|
||||
- P2P gateway deprecated in favor of https://mdavidsaver.github.io/p4p/gw.html
|
||||
|
||||
Release 1.2.2 (Nov 2019)
|
||||
========================
|
||||
|
||||
- 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
|
||||
- Requires pvDataCPP >= 7.1.0
|
||||
- Requires pvAccessCPP >= 6.1.0
|
||||
- Removals
|
||||
- Drop the broken ioccircle and ioccircle2 examples.
|
||||
- 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.
|
||||
- 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)
|
||||
- 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)
|
||||
========================
|
||||
|
||||
-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
|
||||
@@ -14,8 +14,6 @@
|
||||
#include "weakmap.h"
|
||||
#include "weakset.h"
|
||||
|
||||
#include <shareLib.h>
|
||||
|
||||
struct ChannelCache;
|
||||
struct ChannelCacheEntry;
|
||||
struct MonitorUser;
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
#include <pv/pvAccess.h>
|
||||
|
||||
#include <shareLib.h>
|
||||
|
||||
#include "chancache.h"
|
||||
|
||||
struct GWChannel : public epics::pvAccess::Channel
|
||||
|
||||
+13
-2
@@ -139,7 +139,7 @@ GWServerChannelProvider::shared_pointer configure_client(ServerConfig& arg, cons
|
||||
|
||||
pva::Configuration::shared_pointer C(pva::ConfigurationBuilder()
|
||||
.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)
|
||||
@@ -163,7 +163,7 @@ pva::ServerContext::shared_pointer configure_server(ServerConfig& arg, const pvd
|
||||
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::PVBoolean>("autoaddrlist")->get())
|
||||
.add("EPICS_PVAS_AUTO_BEACON_ADDR_LIST", conf->getSubFieldT<pvd::PVScalar>("autoaddrlist")->getAs<std::string>())
|
||||
.add("EPICS_PVAS_SERVER_PORT", conf->getSubFieldT<pvd::PVScalar>("serverport")->getAs<pvd::uint16>())
|
||||
.add("EPICS_PVAS_BROADCAST_PORT", conf->getSubFieldT<pvd::PVScalar>("bcastport")->getAs<pvd::uint16>())
|
||||
.add("EPICS_PVA_DEBUG", arg.debug>=5 ? 5 : 0)
|
||||
@@ -258,6 +258,17 @@ 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;
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
#include <pv/pvAccess.h>
|
||||
|
||||
#include <shareLib.h>
|
||||
|
||||
typedef epicsGuard<epicsMutex> Guard;
|
||||
typedef epicsGuardRelease<epicsMutex> UnGuard;
|
||||
|
||||
|
||||
+1
-7
@@ -64,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)
|
||||
{
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
#include "chancache.h"
|
||||
#include "channel.h"
|
||||
|
||||
#include <shareLib.h>
|
||||
|
||||
struct GWServerChannelProvider :
|
||||
public epics::pvAccess::ChannelProvider,
|
||||
public epics::pvAccess::ChannelFind,
|
||||
|
||||
+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);
|
||||
|
||||
+9
-1
@@ -16,6 +16,7 @@ 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
|
||||
@@ -59,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
|
||||
|
||||
@@ -73,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
|
||||
|
||||
+15
-7
@@ -14,7 +14,6 @@
|
||||
#include <pv/configuration.h>
|
||||
#include <pv/json.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include "pdbgroup.h"
|
||||
|
||||
namespace {
|
||||
@@ -27,6 +26,7 @@ typedef std::map<std::string, pvd::AnyScalar> options_t;
|
||||
typedef std::map<std::string, options_t> config_t;
|
||||
|
||||
struct context {
|
||||
const std::string chanprefix;
|
||||
std::string msg;
|
||||
std::string group, field, key;
|
||||
unsigned depth; // number of '{'s
|
||||
@@ -35,9 +35,13 @@ struct context {
|
||||
// depth 2 - Group
|
||||
// depth 3 - field
|
||||
|
||||
context() :depth(0u) {}
|
||||
context(const std::string& chanprefix, GroupConfig& conf)
|
||||
:chanprefix(chanprefix)
|
||||
,depth(0u)
|
||||
,conf(conf)
|
||||
{}
|
||||
|
||||
GroupConfig conf;
|
||||
GroupConfig& conf;
|
||||
|
||||
void can_assign()
|
||||
{
|
||||
@@ -70,7 +74,7 @@ struct context {
|
||||
fld.type = value.ref<std::string>();
|
||||
|
||||
} else if(key=="+channel") {
|
||||
fld.channel = value.ref<std::string>();
|
||||
fld.channel = chanprefix + value.ref<std::string>();
|
||||
|
||||
} else if(key=="+id") {
|
||||
fld.id = value.ref<std::string>();
|
||||
@@ -218,6 +222,7 @@ struct handler {
|
||||
}// namespace
|
||||
|
||||
void GroupConfig::parse(const char *txt,
|
||||
const char *recname,
|
||||
GroupConfig& result)
|
||||
{
|
||||
#ifndef EPICS_YAJL_VERSION
|
||||
@@ -229,7 +234,12 @@ void GroupConfig::parse(const char *txt,
|
||||
|
||||
std::istringstream strm(txt);
|
||||
|
||||
context ctxt;
|
||||
std::string chanprefix;
|
||||
if(recname) {
|
||||
chanprefix = recname;
|
||||
chanprefix += '.';
|
||||
}
|
||||
context ctxt(chanprefix, result);
|
||||
|
||||
#ifndef EPICS_YAJL_VERSION
|
||||
handler handle(yajl_alloc(&conf_cbs, &conf, NULL, &ctxt));
|
||||
@@ -241,6 +251,4 @@ void GroupConfig::parse(const char *txt,
|
||||
|
||||
if(!pvd::yajl_parse_helper(strm, handle))
|
||||
throw std::runtime_error(ctxt.msg);
|
||||
|
||||
ctxt.conf.swap(result);
|
||||
}
|
||||
|
||||
+2
-1
@@ -13,7 +13,6 @@
|
||||
#include <pv/pvData.h>
|
||||
#include <pv/anyscalar.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include "pvif.h"
|
||||
|
||||
namespace pvd = epics::pvData;
|
||||
@@ -25,8 +24,10 @@ 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;
|
||||
|
||||
+162
-104
@@ -19,7 +19,6 @@
|
||||
#include <pv/pvAccess.h>
|
||||
#include <pv/configuration.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include "helper.h"
|
||||
#include "pdbsingle.h"
|
||||
#include "pvif.h"
|
||||
@@ -60,16 +59,14 @@ struct Splitter {
|
||||
};
|
||||
|
||||
struct GroupMemberInfo {
|
||||
// consumes builder
|
||||
GroupMemberInfo(const std::string& a, const std::string& b, const std::tr1::shared_ptr<PVIFBuilder>& builder)
|
||||
:pvname(a), pvfldname(b), builder(builder), putorder(0) {}
|
||||
GroupMemberInfo() :putorder(0) {}
|
||||
|
||||
std::string pvname, // aka. name passed to dbChannelOpen()
|
||||
pvfldname; // PVStructure sub-field
|
||||
std::string structID; // ID to assign to sub-field
|
||||
std::string type; // mapping type
|
||||
typedef std::set<std::string> triggers_t;
|
||||
triggers_t triggers; // names in GroupInfo::members_names which are post()d on events from pvfldname
|
||||
std::tr1::shared_ptr<PVIFBuilder> builder; // not actually shared, but allows us to be copyable
|
||||
int putorder;
|
||||
|
||||
bool operator<(const GroupMemberInfo& o) const {
|
||||
@@ -101,9 +98,6 @@ struct PDBProcessor
|
||||
typedef std::map<std::string, GroupInfo> groups_t;
|
||||
groups_t groups;
|
||||
|
||||
std::string recbase;
|
||||
GroupInfo *curgroup;
|
||||
|
||||
// validate trigger mappings and process into bit map form
|
||||
void resolveTriggers()
|
||||
{
|
||||
@@ -178,8 +172,13 @@ struct PDBProcessor
|
||||
}
|
||||
}
|
||||
|
||||
PDBProcessor() : curgroup(NULL)
|
||||
PDBProcessor()
|
||||
{
|
||||
#ifdef USE_MULTILOCK
|
||||
GroupConfig conf;
|
||||
#endif
|
||||
|
||||
// process info(Q:Group, ...)
|
||||
for(pdbRecordIterator rec; !rec.done(); rec.next())
|
||||
{
|
||||
const char *json = rec.info("Q:group");
|
||||
@@ -190,107 +189,154 @@ struct PDBProcessor
|
||||
warned = true;
|
||||
fprintf(stderr, "%s: ignoring info(Q:Group, ...\n", rec.name());
|
||||
}
|
||||
#else
|
||||
#endif
|
||||
if(PDBProviderDebug>2) {
|
||||
fprintf(stderr, "%s: info(Q:Group, ...\n", rec.name());
|
||||
}
|
||||
|
||||
#ifdef USE_MULTILOCK
|
||||
try {
|
||||
GroupConfig conf;
|
||||
GroupConfig::parse(json, conf);
|
||||
GroupConfig::parse(json, rec.name(), conf);
|
||||
if(!conf.warning.empty())
|
||||
fprintf(stderr, "%s: warning(s) from info(Q:group, ...\n%s", rec.name(), conf.warning.c_str());
|
||||
|
||||
recbase = rec.name();
|
||||
recbase += ".";
|
||||
|
||||
for(GroupConfig::groups_t::const_iterator git=conf.groups.begin(), gend=conf.groups.end();
|
||||
git!=gend; ++git)
|
||||
{
|
||||
const std::string& grpname = git->first;
|
||||
const GroupConfig::Group& grp = git->second;
|
||||
|
||||
if(dbChannelTest(grpname.c_str())==0) {
|
||||
fprintf(stderr, "%s : Error: Group name conflicts with record name. Ignoring...\n", grpname.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
groups_t::iterator it = groups.find(grpname);
|
||||
if(it==groups.end()) {
|
||||
// lazy creation of group
|
||||
std::pair<groups_t::iterator, bool> ins(groups.insert(std::make_pair(grpname, GroupInfo(grpname))));
|
||||
it = ins.first;
|
||||
}
|
||||
curgroup = &it->second;
|
||||
if(!grp.id.empty())
|
||||
curgroup->structID = grp.id;
|
||||
|
||||
for(GroupConfig::Group::fields_t::const_iterator fit=grp.fields.begin(), fend=grp.fields.end();
|
||||
fit!=fend; ++fit)
|
||||
{
|
||||
const std::string& fldname = fit->first;
|
||||
const GroupConfig::Field& fld = fit->second;
|
||||
|
||||
GroupInfo::members_map_t::const_iterator oldgrp(curgroup->members_map.find(fldname));
|
||||
if(oldgrp!=curgroup->members_map.end()) {
|
||||
fprintf(stderr, "%s.%s Warning: ignoring duplicate mapping %s%s\n",
|
||||
grpname.c_str(), fldname.c_str(),
|
||||
recbase.c_str(), fld.channel.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
std::tr1::shared_ptr<PVIFBuilder> builder(PVIFBuilder::create(fld.type));
|
||||
|
||||
curgroup->members.push_back(GroupMemberInfo(fld.channel.empty() ? fld.channel : recbase + fld.channel, fldname, builder));
|
||||
curgroup->members.back().structID = fld.id;
|
||||
curgroup->members.back().putorder = fld.putorder;
|
||||
curgroup->members_map[fldname] = (size_t)-1; // placeholder see below
|
||||
|
||||
if(PDBProviderDebug>2) {
|
||||
fprintf(stderr, " pdb map '%s.%s' <-> '%s'\n",
|
||||
curgroup->name.c_str(),
|
||||
curgroup->members.back().pvfldname.c_str(),
|
||||
curgroup->members.back().pvname.c_str());
|
||||
}
|
||||
|
||||
if(!fld.trigger.empty()) {
|
||||
GroupInfo::triggers_t::iterator it = curgroup->triggers.find(fldname);
|
||||
if(it==curgroup->triggers.end()) {
|
||||
std::pair<GroupInfo::triggers_t::iterator, bool> ins(curgroup->triggers.insert(
|
||||
std::make_pair(fldname, GroupInfo::triggers_set_t())));
|
||||
it = ins.first;
|
||||
}
|
||||
|
||||
Splitter sep(fld.trigger.c_str(), ',');
|
||||
std::string target;
|
||||
|
||||
while(sep.snip(target)) {
|
||||
curgroup->hastriggers = true;
|
||||
it->second.insert(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(grp.atomic_set) {
|
||||
GroupInfo::tribool V = grp.atomic ? GroupInfo::True : GroupInfo::False;
|
||||
|
||||
if(curgroup->atomic!=GroupInfo::Unset && curgroup->atomic!=V)
|
||||
fprintf(stderr, "%s Warning: pdb atomic setting inconsistent '%s'\n",
|
||||
grpname.c_str(), curgroup->name.c_str());
|
||||
|
||||
curgroup->atomic=V;
|
||||
|
||||
if(PDBProviderDebug>2)
|
||||
fprintf(stderr, " pdb atomic '%s' %s\n",
|
||||
curgroup->name.c_str(), curgroup->atomic ? "YES" : "NO");
|
||||
}
|
||||
}
|
||||
|
||||
}catch(std::exception& e){
|
||||
fprintf(stderr, "%s: Error parsing info(\"Q:group\", ... : %s\n",
|
||||
rec.record()->name, e.what());
|
||||
}
|
||||
#endif // USE_MULTILOCK
|
||||
#endif
|
||||
}
|
||||
|
||||
// process group definition files
|
||||
for(PDBProvider::group_files_t::const_iterator it(PDBProvider::group_files.begin()), end(PDBProvider::group_files.end());
|
||||
it != end; ++it)
|
||||
{
|
||||
std::ifstream jfile(it->c_str());
|
||||
if(!jfile.is_open()) {
|
||||
fprintf(stderr, "Error opening \"%s\"\n", it->c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<char> contents;
|
||||
size_t pos=0u;
|
||||
while(true) {
|
||||
contents.resize(pos+1024u);
|
||||
if(!jfile.read(&contents[pos], contents.size()-pos))
|
||||
break;
|
||||
pos += jfile.gcount();
|
||||
}
|
||||
|
||||
if(jfile.bad() || !jfile.eof()) {
|
||||
fprintf(stderr, "Error reading \"%s\"\n", it->c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
contents.push_back('\0');
|
||||
const char *json = &contents[0];
|
||||
|
||||
if(PDBProviderDebug>2) {
|
||||
fprintf(stderr, "Process dbGroup file \"%s\"\n", it->c_str());
|
||||
}
|
||||
|
||||
#ifdef USE_MULTILOCK
|
||||
try {
|
||||
GroupConfig::parse(json, "", conf);
|
||||
if(!conf.warning.empty())
|
||||
fprintf(stderr, "warning(s) from dbGroup file \"%s\"\n%s", it->c_str(), conf.warning.c_str());
|
||||
}catch(std::exception& e){
|
||||
fprintf(stderr, "Error from dbGroup file \"%s\"\n%s", it->c_str(), e.what());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_MULTILOCK
|
||||
for(GroupConfig::groups_t::const_iterator git=conf.groups.begin(), gend=conf.groups.end();
|
||||
git!=gend; ++git)
|
||||
{
|
||||
const std::string& grpname = git->first;
|
||||
const GroupConfig::Group& grp = git->second;
|
||||
try {
|
||||
|
||||
if(dbChannelTest(grpname.c_str())==0) {
|
||||
fprintf(stderr, "%s : Error: Group name conflicts with record name. Ignoring...\n", grpname.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
groups_t::iterator it = groups.find(grpname);
|
||||
if(it==groups.end()) {
|
||||
// lazy creation of group
|
||||
std::pair<groups_t::iterator, bool> ins(groups.insert(std::make_pair(grpname, GroupInfo(grpname))));
|
||||
it = ins.first;
|
||||
}
|
||||
GroupInfo *curgroup = &it->second;
|
||||
|
||||
if(!grp.id.empty())
|
||||
curgroup->structID = grp.id;
|
||||
|
||||
for(GroupConfig::Group::fields_t::const_iterator fit=grp.fields.begin(), fend=grp.fields.end();
|
||||
fit!=fend; ++fit)
|
||||
{
|
||||
const std::string& fldname = fit->first;
|
||||
const GroupConfig::Field& fld = fit->second;
|
||||
|
||||
if(curgroup->members_map.find(fldname) != curgroup->members_map.end()) {
|
||||
fprintf(stderr, "%s.%s Warning: ignoring duplicate mapping %s\n",
|
||||
grpname.c_str(), fldname.c_str(),
|
||||
fld.channel.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
curgroup->members.push_back(GroupMemberInfo());
|
||||
GroupMemberInfo& info = curgroup->members.back();
|
||||
info.pvname = fld.channel;
|
||||
info.pvfldname = fldname;
|
||||
info.structID = fld.id;
|
||||
info.putorder = fld.putorder;
|
||||
info.type = fld.type;
|
||||
curgroup->members_map[fldname] = (size_t)-1; // placeholder see below
|
||||
|
||||
if(PDBProviderDebug>2) {
|
||||
fprintf(stderr, " pdb map '%s.%s' <-> '%s'\n",
|
||||
curgroup->name.c_str(),
|
||||
curgroup->members.back().pvfldname.c_str(),
|
||||
curgroup->members.back().pvname.c_str());
|
||||
}
|
||||
|
||||
if(!fld.trigger.empty()) {
|
||||
GroupInfo::triggers_t::iterator it = curgroup->triggers.find(fldname);
|
||||
if(it==curgroup->triggers.end()) {
|
||||
std::pair<GroupInfo::triggers_t::iterator, bool> ins(curgroup->triggers.insert(
|
||||
std::make_pair(fldname, GroupInfo::triggers_set_t())));
|
||||
it = ins.first;
|
||||
}
|
||||
|
||||
Splitter sep(fld.trigger.c_str(), ',');
|
||||
std::string target;
|
||||
|
||||
while(sep.snip(target)) {
|
||||
curgroup->hastriggers = true;
|
||||
it->second.insert(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(grp.atomic_set) {
|
||||
GroupInfo::tribool V = grp.atomic ? GroupInfo::True : GroupInfo::False;
|
||||
|
||||
if(curgroup->atomic!=GroupInfo::Unset && curgroup->atomic!=V)
|
||||
fprintf(stderr, "%s Warning: pdb atomic setting inconsistent '%s'\n",
|
||||
grpname.c_str(), curgroup->name.c_str());
|
||||
|
||||
curgroup->atomic=V;
|
||||
|
||||
if(PDBProviderDebug>2)
|
||||
fprintf(stderr, " pdb atomic '%s' %s\n",
|
||||
curgroup->name.c_str(), curgroup->atomic ? "YES" : "NO");
|
||||
}
|
||||
|
||||
}catch(std::exception& e){
|
||||
fprintf(stderr, "Error processing Q:group \"%s\" : %s\n",
|
||||
grpname.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// re-sort GroupInfo::members to ensure the shorter names appear first
|
||||
@@ -312,6 +358,7 @@ struct PDBProcessor
|
||||
resolveTriggers();
|
||||
// must not re-sort members after this point as resolveTriggers()
|
||||
// has stored array indicies.
|
||||
#endif
|
||||
}
|
||||
|
||||
};
|
||||
@@ -319,6 +366,8 @@ struct PDBProcessor
|
||||
|
||||
size_t PDBProvider::num_instances;
|
||||
|
||||
std::list<std::string> PDBProvider::group_files;
|
||||
|
||||
PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_pointer &)
|
||||
{
|
||||
/* Long view
|
||||
@@ -406,10 +455,12 @@ PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_poin
|
||||
chan.swap(temp);
|
||||
}
|
||||
|
||||
std::tr1::shared_ptr<PVIFBuilder> pvifbuilder(PVIFBuilder::create(mem.type, chan.chan));
|
||||
|
||||
if(!parts.empty())
|
||||
builder = mem.builder->dtype(builder, parts.back().name, chan);
|
||||
builder = pvifbuilder->dtype(builder, parts.back().name);
|
||||
else
|
||||
builder = mem.builder->dtype(builder, "", chan);
|
||||
builder = pvifbuilder->dtype(builder, "");
|
||||
|
||||
if(!parts.empty()) {
|
||||
for(size_t j=0; j<parts.size()-1; j++)
|
||||
@@ -420,8 +471,14 @@ 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);
|
||||
info.builder = PTRMOVE(pvifbuilder);
|
||||
assert(info.builder.get());
|
||||
|
||||
info.attachment.swap(parts);
|
||||
@@ -512,10 +569,11 @@ PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_poin
|
||||
info.evt_VALUE.self = info.evt_PROPERTY.self = pv;
|
||||
assert(info.chan);
|
||||
|
||||
info.pvif.reset(info.builder->attach(info.chan, pv->complete, info.attachment));
|
||||
info.pvif.reset(info.builder->attach(pv->complete, info.attachment));
|
||||
|
||||
// TODO: don't need evt_PROPERTY for PVIF plain
|
||||
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);
|
||||
|
||||
+6
-3
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "weakmap.h"
|
||||
|
||||
#include <shareLib.h>
|
||||
#include <pv/qsrv.h>
|
||||
|
||||
struct PDBProvider;
|
||||
|
||||
@@ -30,7 +30,7 @@ struct PDBPV
|
||||
virtual void show(int lvl) {}
|
||||
};
|
||||
|
||||
struct epicsShareClass PDBProvider : public epics::pvAccess::ChannelProvider,
|
||||
struct QSRV_API PDBProvider : public epics::pvAccess::ChannelProvider,
|
||||
public epics::pvAccess::ChannelFind,
|
||||
public std::tr1::enable_shared_from_this<PDBProvider>
|
||||
{
|
||||
@@ -64,10 +64,13 @@ struct epicsShareClass PDBProvider : public epics::pvAccess::ChannelProvider,
|
||||
|
||||
dbEventCtx event_context;
|
||||
|
||||
typedef std::list<std::string> group_files_t;
|
||||
static group_files_t group_files;
|
||||
|
||||
static size_t num_instances;
|
||||
};
|
||||
|
||||
epicsShareFunc
|
||||
QSRV_API
|
||||
void QSRVRegistrar_counters();
|
||||
|
||||
#endif // PDB_H
|
||||
|
||||
+20
-7
@@ -13,7 +13,6 @@
|
||||
#include <pv/configuration.h>
|
||||
#include <pv/epicsException.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include "helper.h"
|
||||
#include "pdbgroup.h"
|
||||
#include "pdb.h"
|
||||
@@ -139,6 +138,15 @@ 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;
|
||||
}
|
||||
|
||||
@@ -326,7 +334,7 @@ PDBGroupPut::PDBGroupPut(const PDBGroupChannel::shared_pointer& channel,
|
||||
{
|
||||
PDBGroupPV::Info& info = channel->pv->members[i];
|
||||
|
||||
pvif[i].reset(info.builder->attach(info.chan, pvf, info.attachment));
|
||||
pvif[i].reset(info.builder->attach(pvf, info.attachment));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,7 +355,7 @@ void PDBGroupPut::put(pvd::PVStructure::shared_pointer const & value,
|
||||
PDBGroupPV::Info& info = channel->pv->members[i];
|
||||
if(!info.allowProc) continue;
|
||||
|
||||
putpvif[i].reset(info.builder->attach(info.chan, value, info.attachment));
|
||||
putpvif[i].reset(info.builder->attach(value, info.attachment));
|
||||
}
|
||||
|
||||
pvd::Status ret;
|
||||
@@ -368,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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,8 +394,10 @@ void PDBGroupPut::get()
|
||||
changed->clear();
|
||||
if(atomic) {
|
||||
DBManyLocker L(channel->pv->locker);
|
||||
for(size_t i=0; i<npvs; i++)
|
||||
pvif[i]->put(*changed, DBE_VALUE|DBE_ALARM|DBE_PROPERTY, NULL);
|
||||
for(size_t i=0; i<npvs; i++) {
|
||||
LocalFL FL(NULL, channel->pv->members[i].chan);
|
||||
pvif[i]->put(*changed, DBE_VALUE|DBE_ALARM|DBE_PROPERTY, FL.pfl);
|
||||
}
|
||||
} else {
|
||||
|
||||
for(size_t i=0; i<npvs; i++)
|
||||
@@ -393,7 +405,8 @@ void PDBGroupPut::get()
|
||||
PDBGroupPV::Info& info = channel->pv->members[i];
|
||||
|
||||
DBScanLocker L(dbChannelRecord(info.chan));
|
||||
pvif[i]->put(*changed, DBE_VALUE|DBE_ALARM|DBE_PROPERTY, NULL);
|
||||
LocalFL FL(NULL, info.chan);
|
||||
pvif[i]->put(*changed, DBE_VALUE|DBE_ALARM|DBE_PROPERTY, FL.pfl);
|
||||
}
|
||||
}
|
||||
//TODO: report unused fields as changed?
|
||||
|
||||
+11
-8
@@ -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;
|
||||
@@ -61,7 +59,7 @@ struct epicsShareClass GroupConfig
|
||||
std::swap(warning, o.warning);
|
||||
}
|
||||
|
||||
static void parse(const char *txt,
|
||||
static void parse(const char *txt, const char *recname,
|
||||
GroupConfig& result);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
|
||||
+37
-14
@@ -5,15 +5,16 @@
|
||||
#include <dbAccess.h>
|
||||
#include <dbChannel.h>
|
||||
#include <dbStaticLib.h>
|
||||
#include <epicsAtomic.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"
|
||||
@@ -95,19 +96,23 @@ void pdb_single_event(void *user_arg, struct dbChannel *chan,
|
||||
PDBSinglePV::PDBSinglePV(DBCH& chan,
|
||||
const PDBProvider::shared_pointer& prov)
|
||||
:provider(prov)
|
||||
,builder(new ScalarBuilder)
|
||||
,builder(new ScalarBuilder(chan.chan))
|
||||
,interested_iterating(false)
|
||||
,evt_VALUE(this)
|
||||
,evt_PROPERTY(this)
|
||||
,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));
|
||||
fielddesc = std::tr1::static_pointer_cast<const pvd::Structure>(builder->dtype());
|
||||
|
||||
complete = pvd::getPVDataCreate()->createPVStructure(fielddesc);
|
||||
FieldName temp;
|
||||
pvif.reset(builder->attach(this->chan, complete, temp));
|
||||
pvif.reset(builder->attach(complete, temp));
|
||||
|
||||
epics::atomic::increment(num_instances);
|
||||
}
|
||||
@@ -119,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
|
||||
@@ -128,6 +134,11 @@ 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;
|
||||
}
|
||||
|
||||
@@ -145,7 +156,7 @@ void PDBSinglePV::addMonitor(PDBSingleMonitor* mon)
|
||||
db_post_single_event(evt_VALUE.subscript);
|
||||
db_post_single_event(evt_PROPERTY.subscript);
|
||||
|
||||
} if(hadevent_VALUE && hadevent_PROPERTY) {
|
||||
} 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
|
||||
@@ -200,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
|
||||
@@ -289,7 +308,7 @@ PDBSinglePut::PDBSinglePut(const PDBSingleChannel::shared_pointer &channel,
|
||||
,requester(requester)
|
||||
,changed(new pvd::BitSet(channel->fielddesc->getNumberFields()))
|
||||
,pvf(pvd::getPVDataCreate()->createPVStructure(channel->fielddesc))
|
||||
,pvif(channel->pv->builder->attach(channel->pv->chan, pvf, FieldName()))
|
||||
,pvif(channel->pv->builder->attach(pvf, FieldName()))
|
||||
,notifyBusy(0)
|
||||
,doProc(PVIF::ProcPassive)
|
||||
,doWait(false)
|
||||
@@ -337,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);
|
||||
@@ -353,7 +375,7 @@ void PDBSinglePut::put(pvd::PVStructure::shared_pointer const & value,
|
||||
// TODO: dbNotify doesn't allow us for force processing
|
||||
|
||||
// assume value may be a different struct each time
|
||||
p2p::auto_ptr<PVIF> putpvif(channel->pv->builder->attach(channel->pv->chan, value, FieldName()));
|
||||
p2p::auto_ptr<PVIF> putpvif(channel->pv->builder->attach(value, FieldName()));
|
||||
unsigned mask = putpvif->dbe(*changed);
|
||||
|
||||
if(mask!=DBE_VALUE) {
|
||||
@@ -375,10 +397,10 @@ void PDBSinglePut::put(pvd::PVStructure::shared_pointer const & value,
|
||||
return; // skip notification
|
||||
} else {
|
||||
// assume value may be a different struct each time
|
||||
p2p::auto_ptr<PVIF> putpvif(channel->pv->builder->attach(channel->pv->chan, value, FieldName()));
|
||||
p2p::auto_ptr<PVIF> putpvif(channel->pv->builder->attach(value, FieldName()));
|
||||
try{
|
||||
DBScanLocker L(chan);
|
||||
putpvif->get(*changed, doProc);
|
||||
ret = putpvif->get(*changed, doProc);
|
||||
|
||||
}catch(std::runtime_error& e){
|
||||
ret = pvd::Status::error(e.what());
|
||||
@@ -404,7 +426,8 @@ void PDBSinglePut::get()
|
||||
changed->clear();
|
||||
{
|
||||
DBScanLocker L(pvif->chan);
|
||||
pvif->put(*changed, DBE_VALUE|DBE_ALARM|DBE_PROPERTY, NULL);
|
||||
LocalFL FL(NULL, pvif->chan);
|
||||
pvif->put(*changed, DBE_VALUE|DBE_ALARM|DBE_PROPERTY, FL.pfl);
|
||||
}
|
||||
//TODO: report unused fields as changed?
|
||||
changed->clear();
|
||||
|
||||
+7
-3
@@ -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()
|
||||
@@ -78,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;
|
||||
|
||||
|
||||
+31
-6
@@ -2,7 +2,6 @@
|
||||
#define PV_QSRV_H
|
||||
|
||||
#include <epicsVersion.h>
|
||||
#include <shareLib.h>
|
||||
|
||||
#ifndef VERSION_INT
|
||||
# define VERSION_INT(V,R,M,P) ( ((V)<<24) | ((R)<<16) | ((M)<<8) | (P))
|
||||
@@ -15,6 +14,29 @@
|
||||
|
||||
#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
|
||||
@@ -22,12 +44,15 @@ extern "C" {
|
||||
struct link; /* aka. DBLINK from link.h */
|
||||
|
||||
/** returns QSRV_VERSION_INT captured at compilation time */
|
||||
epicsShareExtern unsigned qsrvVersion(void);
|
||||
QSRV_API unsigned qsrvVersion(void);
|
||||
|
||||
/** returns QSRV_ABI_VERSION_INT captured at compilation time */
|
||||
epicsShareExtern unsigned qsrvABIVersion(void);
|
||||
QSRV_API unsigned qsrvABIVersion(void);
|
||||
|
||||
epicsShareFunc void testqsrvWaitForLinkEvent(struct link *plink);
|
||||
QSRV_API
|
||||
long dbLoadGroup(const char* fname);
|
||||
|
||||
QSRV_API void testqsrvWaitForLinkEvent(struct link *plink);
|
||||
|
||||
/** Call before testIocShutdownOk()
|
||||
@code
|
||||
@@ -41,7 +66,7 @@ epicsShareFunc void testqsrvWaitForLinkEvent(struct link *plink);
|
||||
testdbCleanup();
|
||||
@endcode
|
||||
*/
|
||||
epicsShareExtern void testqsrvShutdownOk(void);
|
||||
QSRV_API void testqsrvShutdownOk(void);
|
||||
|
||||
/** Call after testIocShutdownOk() and before testdbCleanup()
|
||||
@code
|
||||
@@ -55,7 +80,7 @@ epicsShareExtern void testqsrvShutdownOk(void);
|
||||
testdbCleanup();
|
||||
@endcode
|
||||
*/
|
||||
epicsShareExtern void testqsrvCleanup(void);
|
||||
QSRV_API void testqsrvCleanup(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+1
-2
@@ -26,13 +26,12 @@
|
||||
#include <pv/reftrack.h>
|
||||
#include <pva/client.h>
|
||||
|
||||
#include <epicsExport.h> /* defines epicsExportSharedSymbols */
|
||||
|
||||
#include "pv/qsrv.h"
|
||||
#include "helper.h"
|
||||
#include "pvif.h"
|
||||
#include "pvalink.h"
|
||||
|
||||
#include <epicsExport.h> /* defines epicsExportSharedSymbols */
|
||||
|
||||
int pvaLinkDebug;
|
||||
int pvaLinkIsolate;
|
||||
|
||||
+3
-13
@@ -4,11 +4,6 @@
|
||||
#include <set>
|
||||
#include <map>
|
||||
|
||||
#ifdef epicsExportSharedSymbols
|
||||
# define pvalinkEpicsExportSharedSymbols
|
||||
# undef epicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#define EPICS_DBCA_PRIVATE_API
|
||||
#include <epicsGuard.h>
|
||||
#include <dbAccess.h>
|
||||
@@ -44,19 +39,14 @@
|
||||
|
||||
#include <pv/sharedPtr.h>
|
||||
|
||||
#ifdef pvalinkEpicsExportSharedSymbols
|
||||
# define epicsExportSharedSymbols
|
||||
# undef pvalinkEpicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include "helper.h"
|
||||
#include "pvif.h"
|
||||
#include "tpool.h"
|
||||
|
||||
extern "C" {
|
||||
epicsShareExtern int pvaLinkDebug;
|
||||
epicsShareExtern int pvaLinkIsolate;
|
||||
epicsShareExtern int pvaLinkNWorkers;
|
||||
QSRV_API extern int pvaLinkDebug;
|
||||
QSRV_API extern int pvaLinkIsolate;
|
||||
QSRV_API extern int pvaLinkNWorkers;
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
|
||||
#include <pv/reftrack.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include <shareLib.h>
|
||||
|
||||
#include "pvalink.h"
|
||||
|
||||
int pvaLinkNWorkers = 1;
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
#include <epicsStdio.h> // redirects stdout/stderr
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include <shareLib.h>
|
||||
|
||||
#include "pvalink.h"
|
||||
|
||||
namespace pvalink {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#include <pv/reftrack.h>
|
||||
#include <alarm.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include <shareLib.h>
|
||||
|
||||
#include "pvalink.h"
|
||||
|
||||
namespace pvalink {
|
||||
@@ -92,7 +89,9 @@ pvd::PVField::const_shared_pointer pvaLink::getSubField(const char *name)
|
||||
} else {
|
||||
// we access a sub-struct
|
||||
ret = lchan->op_mon.root->getSubField(fieldName);
|
||||
if(ret->getField()->getType()!=pvd::structure) {
|
||||
if(!ret) {
|
||||
// noop
|
||||
} else if(ret->getField()->getType()!=pvd::structure) {
|
||||
// addressed sub-field isn't a sub-structure
|
||||
if(strcmp(name, "value")!=0) {
|
||||
// unless we are trying to fetch the "value", we fail here
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
|
||||
#include <pv/current_function.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include <shareLib.h>
|
||||
|
||||
#include "pvalink.h"
|
||||
|
||||
|
||||
@@ -389,8 +386,10 @@ 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;
|
||||
|
||||
+284
-133
@@ -11,14 +11,17 @@
|
||||
#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"
|
||||
|
||||
@@ -27,14 +30,13 @@
|
||||
#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)
|
||||
{
|
||||
@@ -67,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)
|
||||
{}
|
||||
@@ -92,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;
|
||||
|
||||
@@ -173,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())
|
||||
@@ -185,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);
|
||||
@@ -441,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);
|
||||
@@ -526,16 +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){
|
||||
pvmeta.nsecMask = 0;
|
||||
std::cerr<<dbChannelRecord(chan)->name<<" : Q:time:tag nsec:lsb: requires a number not '"<<UT[9]<<"'\n";
|
||||
std::cerr<<info.name()<<" : Q:time:tag nsec:lsb: requires a number not '"<<UT[9]<<"'\n";
|
||||
}
|
||||
}
|
||||
if(pvmeta.nsecMask>0 && pvmeta.nsecMask<=32) {
|
||||
@@ -551,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
|
||||
{
|
||||
@@ -581,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() {}
|
||||
|
||||
@@ -603,15 +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;
|
||||
pvd::Status ret = checkDISP(chan);
|
||||
if(!ret)
|
||||
return ret;
|
||||
|
||||
bool newval = mask.logical_and(pvmeta.maskVALUEPut);
|
||||
if(newval) {
|
||||
getValue(pvmeta.chan, pvmeta.value.get());
|
||||
if(permit)
|
||||
getValue(pvmeta.chan, pvmeta.value.get());
|
||||
else
|
||||
ret = pvd::Status::error("Put not permitted");
|
||||
}
|
||||
if(newval || proc==PVIF::ProcForce) {
|
||||
ret = PVIF::get(mask, proc);
|
||||
if(permit)
|
||||
ret = PVIF::get(mask, proc);
|
||||
else
|
||||
ret = pvd::Status::error("Process not permitted");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -651,15 +761,11 @@ short PVD2DBR(pvd::ScalarType pvt)
|
||||
{
|
||||
switch(pvt) {
|
||||
#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case pvd::pv##PVACODE: return DBR_##DBFTYPE;
|
||||
#ifdef USE_INT64
|
||||
# define CASE_REAL_INT64
|
||||
#else
|
||||
#ifndef USE_INT64
|
||||
# define CASE_SQUEEZE_INT64
|
||||
#endif
|
||||
#include "pv/typemap.h"
|
||||
#ifdef USE_INT64
|
||||
# undef CASE_REAL_INT64
|
||||
#else
|
||||
#ifndef USE_INT64
|
||||
# undef CASE_SQUEEZE_INT64
|
||||
#endif
|
||||
#undef CASE
|
||||
@@ -669,7 +775,7 @@ short PVD2DBR(pvd::ScalarType pvt)
|
||||
}
|
||||
|
||||
epics::pvData::FieldConstPtr
|
||||
ScalarBuilder::dtype(dbChannel *channel)
|
||||
ScalarBuilder::dtype()
|
||||
{
|
||||
short dbr = dbChannelFinalFieldType(channel);
|
||||
const long maxelem = dbChannelFinalElements(channel);
|
||||
@@ -681,23 +787,50 @@ 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*
|
||||
ScalarBuilder::attach(dbChannel *channel, const epics::pvData::PVStructurePtr& root, const FieldName& fldname)
|
||||
ScalarBuilder::attach(const epics::pvData::PVStructurePtr& root, const FieldName& fldname)
|
||||
{
|
||||
if(!channel)
|
||||
throw std::runtime_error("+type:\"scalar\" requires +channel:");
|
||||
@@ -706,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) {
|
||||
@@ -716,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:
|
||||
@@ -736,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);
|
||||
}
|
||||
@@ -767,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);
|
||||
@@ -775,20 +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;
|
||||
pvd::Status ret = checkDISP(chan);
|
||||
if(!ret)
|
||||
return ret;
|
||||
|
||||
bool newval = mask.get(fieldOffset);
|
||||
if(newval) {
|
||||
getValue(channel, field.get());
|
||||
if(permit)
|
||||
getValue(channel, field.get());
|
||||
else
|
||||
ret = pvd::Status::error("Put not permitted");
|
||||
}
|
||||
if(newval || proc==PVIF::ProcForce) {
|
||||
ret = PVIF::get(mask, proc);
|
||||
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.
|
||||
@@ -801,10 +950,11 @@ struct PVIFPlain : public PVIF
|
||||
|
||||
struct PlainBuilder : public PVIFBuilder
|
||||
{
|
||||
explicit PlainBuilder(dbChannel* chan) :PVIFBuilder(chan) {}
|
||||
virtual ~PlainBuilder() {}
|
||||
|
||||
// fetch the structure description
|
||||
virtual epics::pvData::FieldConstPtr dtype(dbChannel *channel) OVERRIDE FINAL {
|
||||
virtual epics::pvData::FieldConstPtr dtype() OVERRIDE FINAL {
|
||||
const short dbr = dbChannelFinalFieldType(channel);
|
||||
const long maxelem = dbChannelFinalElements(channel);
|
||||
const pvd::ScalarType pvt = DBR2PVD(dbr);
|
||||
@@ -821,8 +971,7 @@ struct PlainBuilder : public PVIFBuilder
|
||||
// Attach to a structure instance.
|
||||
// must be of the type returned by dtype().
|
||||
// need not be the root structure
|
||||
virtual PVIF* attach(dbChannel *channel,
|
||||
const epics::pvData::PVStructurePtr& root,
|
||||
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root,
|
||||
const FieldName& fldname) OVERRIDE FINAL
|
||||
{
|
||||
if(!channel)
|
||||
@@ -841,10 +990,11 @@ struct PlainBuilder : public PVIFBuilder
|
||||
|
||||
struct AnyScalarBuilder : public PVIFBuilder
|
||||
{
|
||||
explicit AnyScalarBuilder(dbChannel* chan) :PVIFBuilder(chan) {}
|
||||
virtual ~AnyScalarBuilder() {}
|
||||
|
||||
// fetch the structure description
|
||||
virtual epics::pvData::FieldConstPtr dtype(dbChannel *channel) OVERRIDE FINAL {
|
||||
virtual epics::pvData::FieldConstPtr dtype() OVERRIDE FINAL {
|
||||
(void)channel; //ignored
|
||||
return pvd::getFieldCreate()->createVariantUnion();
|
||||
}
|
||||
@@ -852,8 +1002,7 @@ struct AnyScalarBuilder : public PVIFBuilder
|
||||
// Attach to a structure instance.
|
||||
// must be of the type returned by dtype().
|
||||
// need not be the root structure
|
||||
virtual PVIF* attach(dbChannel *channel,
|
||||
const epics::pvData::PVStructurePtr& root,
|
||||
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root,
|
||||
const FieldName& fldname) OVERRIDE FINAL
|
||||
{
|
||||
if(!channel)
|
||||
@@ -898,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());
|
||||
@@ -910,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)
|
||||
@@ -919,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;
|
||||
@@ -935,16 +1088,16 @@ struct PVIFMeta : public PVIF
|
||||
|
||||
struct MetaBuilder : public PVIFBuilder
|
||||
{
|
||||
explicit MetaBuilder(dbChannel* chan) :PVIFBuilder(chan) {}
|
||||
virtual ~MetaBuilder() {}
|
||||
|
||||
// fetch the structure description
|
||||
virtual epics::pvData::FieldConstPtr dtype(dbChannel *channel) OVERRIDE FINAL {
|
||||
virtual epics::pvData::FieldConstPtr dtype() OVERRIDE FINAL {
|
||||
throw std::logic_error("Don't call me");
|
||||
}
|
||||
|
||||
virtual epics::pvData::FieldBuilderPtr dtype(epics::pvData::FieldBuilderPtr& builder,
|
||||
const std::string& fld,
|
||||
dbChannel *channel)
|
||||
const std::string& fld) OVERRIDE FINAL
|
||||
{
|
||||
pvd::StandardFieldPtr std(pvd::getStandardField());
|
||||
if(fld.empty()) {
|
||||
@@ -961,8 +1114,7 @@ struct MetaBuilder : public PVIFBuilder
|
||||
// Attach to a structure instance.
|
||||
// must be of the type returned by dtype().
|
||||
// need not be the root structure
|
||||
virtual PVIF* attach(dbChannel *channel,
|
||||
const epics::pvData::PVStructurePtr& root,
|
||||
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root,
|
||||
const FieldName& fldname) OVERRIDE FINAL
|
||||
{
|
||||
if(!channel)
|
||||
@@ -980,18 +1132,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;
|
||||
}
|
||||
@@ -999,20 +1151,21 @@ struct PVIFProc : public PVIF
|
||||
|
||||
struct ProcBuilder : public PVIFBuilder
|
||||
{
|
||||
explicit ProcBuilder(dbChannel* chan) :PVIFBuilder(chan) {}
|
||||
virtual ~ProcBuilder() {}
|
||||
|
||||
// fetch the structure description
|
||||
virtual epics::pvData::FieldConstPtr dtype(dbChannel *channel) OVERRIDE FINAL {
|
||||
virtual epics::pvData::FieldConstPtr dtype() OVERRIDE FINAL {
|
||||
throw std::logic_error("Don't call me");
|
||||
}
|
||||
|
||||
virtual epics::pvData::FieldBuilderPtr dtype(epics::pvData::FieldBuilderPtr& builder,
|
||||
const std::string& fld,
|
||||
dbChannel *channel)
|
||||
const std::string& fld) OVERRIDE FINAL
|
||||
{
|
||||
// invisible
|
||||
return builder;
|
||||
}
|
||||
virtual PVIF* attach(dbChannel *channel,
|
||||
const epics::pvData::PVStructurePtr& root,
|
||||
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root,
|
||||
const FieldName& fldname) OVERRIDE FINAL
|
||||
{
|
||||
if(!channel)
|
||||
@@ -1024,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);
|
||||
|
||||
@@ -1036,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);
|
||||
@@ -1060,35 +1216,30 @@ pvd::Status PVIF::get(const epics::pvData::BitSet& mask, proc_t proc)
|
||||
|
||||
epics::pvData::FieldBuilderPtr
|
||||
PVIFBuilder::dtype(epics::pvData::FieldBuilderPtr& builder,
|
||||
const std::string &fld,
|
||||
dbChannel *channel)
|
||||
const std::string &fld)
|
||||
{
|
||||
if(fld.empty())
|
||||
throw std::runtime_error("Can't attach this +type to root");
|
||||
throw std::runtime_error(SB()<<"Can't attach +type "<<typeid(*this).name()<<" to root");
|
||||
|
||||
epics::pvData::FieldConstPtr ftype(this->dtype(channel));
|
||||
epics::pvData::FieldConstPtr ftype(this->dtype());
|
||||
if(ftype)
|
||||
builder = builder->add(fld, ftype);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
PVIFBuilder* PVIFBuilder::create(const std::string& type)
|
||||
PVIFBuilder* PVIFBuilder::create(const std::string& type, dbChannel* chan)
|
||||
{
|
||||
if(type.empty() || type=="scalar")
|
||||
return new ScalarBuilder;
|
||||
return new ScalarBuilder(chan);
|
||||
else if(type=="plain")
|
||||
return new PlainBuilder;
|
||||
return new PlainBuilder(chan);
|
||||
else if(type=="any")
|
||||
return new AnyScalarBuilder;
|
||||
return new AnyScalarBuilder(chan);
|
||||
else if(type=="meta")
|
||||
return new MetaBuilder;
|
||||
return new MetaBuilder(chan);
|
||||
else if(type=="proc")
|
||||
return new ProcBuilder;
|
||||
return new ProcBuilder(chan);
|
||||
else
|
||||
throw std::runtime_error(std::string("Unknown +type=")+type);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
epicsExportAddress(int, qsrvDisableFormat);
|
||||
}
|
||||
|
||||
+66
-19
@@ -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,14 +30,20 @@
|
||||
# define USE_MULTILOCK
|
||||
#endif
|
||||
|
||||
namespace epics {
|
||||
namespace pvAccess {
|
||||
class ChannelRequester;
|
||||
}
|
||||
}
|
||||
|
||||
short PVD2DBR(epics::pvData::ScalarType pvt);
|
||||
|
||||
// copy from PVField (.value sub-field) to DBF buffer
|
||||
epicsShareExtern
|
||||
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)
|
||||
epicsShareExtern
|
||||
QSRV_API
|
||||
long copyDBF2PVD(const epics::pvData::shared_vector<const void>& buf,
|
||||
const epics::pvData::PVField::shared_pointer& out,
|
||||
epics::pvData::BitSet &changed,
|
||||
@@ -52,10 +59,17 @@ 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()
|
||||
@@ -74,6 +88,23 @@ private:
|
||||
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 {
|
||||
DBENTRY ent;
|
||||
pdbRecordInfo(const char *name)
|
||||
@@ -223,6 +254,7 @@ struct LocalFL
|
||||
if(pfl) pfl = dbChannelRunPostChain(pchan, pfl);
|
||||
}
|
||||
}
|
||||
this->pfl = pfl;
|
||||
}
|
||||
~LocalFL() {
|
||||
if(ours) db_delete_field_log(pfl);
|
||||
@@ -247,7 +279,7 @@ struct DBManyLock
|
||||
dbLocker *plock;
|
||||
DBManyLock() :plock(NULL) {}
|
||||
DBManyLock(const std::vector<dbCommon*>& recs, unsigned flags=0)
|
||||
:plock(dbLockerAlloc((dbCommon**)&recs[0], recs.size(), flags))
|
||||
:plock(dbLockerAlloc( (recs.size() > 0 ? (dbCommon**)&recs[0] : NULL), recs.size(), flags))
|
||||
{
|
||||
if(!plock) throw std::invalid_argument("Failed to create locker");
|
||||
}
|
||||
@@ -278,7 +310,7 @@ struct DBManyLocker
|
||||
};
|
||||
#endif
|
||||
|
||||
struct epicsShareClass FieldName
|
||||
struct QSRV_API FieldName
|
||||
{
|
||||
struct Component {
|
||||
std::string name;
|
||||
@@ -321,7 +353,7 @@ private:
|
||||
FieldName& operator=(const FieldName&);
|
||||
};
|
||||
|
||||
struct epicsShareClass PVIF {
|
||||
struct QSRV_API PVIF {
|
||||
PVIF(dbChannel *ch);
|
||||
virtual ~PVIF() {}
|
||||
|
||||
@@ -336,9 +368,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;
|
||||
|
||||
@@ -347,36 +379,51 @@ 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
|
||||
{
|
||||
dbChannel* const channel;
|
||||
|
||||
virtual ~PVIFBuilder() {}
|
||||
|
||||
// fetch the structure description
|
||||
virtual epics::pvData::FieldConstPtr dtype(dbChannel *channel) =0;
|
||||
virtual epics::pvData::FieldConstPtr dtype() =0;
|
||||
|
||||
virtual epics::pvData::FieldBuilderPtr dtype(epics::pvData::FieldBuilderPtr& builder,
|
||||
const std::string& fld,
|
||||
dbChannel *channel);
|
||||
const std::string& fld);
|
||||
|
||||
// Attach to a structure instance.
|
||||
// must be of the type returned by dtype().
|
||||
// must be the root structure
|
||||
virtual PVIF* attach(dbChannel *channel, const epics::pvData::PVStructurePtr& root, const FieldName& fld) =0;
|
||||
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root, const FieldName& fld) =0;
|
||||
|
||||
static PVIFBuilder* create(const std::string& name);
|
||||
// entry point for Builder
|
||||
static PVIFBuilder* create(const std::string& mapname, dbChannel* chan);
|
||||
protected:
|
||||
PVIFBuilder() {}
|
||||
explicit PVIFBuilder(dbChannel* chan) : channel(chan) {}
|
||||
private:
|
||||
PVIFBuilder(const PVIFBuilder&);
|
||||
PVIFBuilder& operator=(const PVIFBuilder&);
|
||||
};
|
||||
|
||||
struct epicsShareClass ScalarBuilder : public PVIFBuilder
|
||||
struct QSRV_API ScalarBuilder : public PVIFBuilder
|
||||
{
|
||||
explicit ScalarBuilder(dbChannel* chan) :PVIFBuilder(chan) {}
|
||||
virtual ~ScalarBuilder() {}
|
||||
|
||||
virtual epics::pvData::FieldConstPtr dtype(dbChannel *channel) OVERRIDE FINAL;
|
||||
virtual PVIF* attach(dbChannel *channel, const epics::pvData::PVStructurePtr& root, const FieldName& fld) OVERRIDE FINAL;
|
||||
virtual epics::pvData::FieldConstPtr dtype() OVERRIDE FINAL;
|
||||
virtual PVIF* attach(const epics::pvData::PVStructurePtr& root, const FieldName& fld) OVERRIDE FINAL;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -9,9 +9,6 @@ link("pva", "lsetPVA")
|
||||
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)
|
||||
|
||||
@@ -8,9 +8,6 @@ registrar(installPVAAddLinkHook)
|
||||
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)
|
||||
|
||||
+42
-2
@@ -18,8 +18,6 @@
|
||||
#include <pv/serverContext.h>
|
||||
#include <pv/iocshelper.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
|
||||
#include "pv/qsrv.h"
|
||||
#include "pvahelper.h"
|
||||
#include "pvif.h"
|
||||
@@ -48,8 +46,49 @@ void QSRVRegistrar_counters()
|
||||
epics::registerRefCounter("PDBProvider", &PDBProvider::num_instances);
|
||||
}
|
||||
|
||||
long dbLoadGroup(const char* fname)
|
||||
{
|
||||
try {
|
||||
if(!fname) {
|
||||
printf("dbLoadGroup(\"file.json\")\n"
|
||||
"\n"
|
||||
"Load additional DB group definitions from file.\n");
|
||||
return 1;
|
||||
}
|
||||
#ifndef USE_MULTILOCK
|
||||
static bool warned;
|
||||
if(!warned) {
|
||||
warned = true;
|
||||
fprintf(stderr, "ignoring %s\n", fname);
|
||||
}
|
||||
#endif
|
||||
|
||||
if(fname[0]=='-') {
|
||||
fname++;
|
||||
if(fname[0]=='*' && fname[1]=='\0') {
|
||||
PDBProvider::group_files.clear();
|
||||
} else {
|
||||
PDBProvider::group_files.remove(fname);
|
||||
}
|
||||
} else {
|
||||
PDBProvider::group_files.remove(fname);
|
||||
PDBProvider::group_files.push_back(fname);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}catch(std::exception& e){
|
||||
fprintf(stderr, "Error: %s\n", e.what());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void dbLoadGroupWrap(const char* fname)
|
||||
{
|
||||
(void)dbLoadGroup(fname);
|
||||
}
|
||||
|
||||
void dbgl(int lvl, const char *pattern)
|
||||
{
|
||||
if(!pattern)
|
||||
@@ -90,6 +129,7 @@ void QSRVRegistrar()
|
||||
QSRVRegistrar_counters();
|
||||
pva::ChannelProviderRegistry::servers()->addSingleton<PDBProvider>("QSRV");
|
||||
epics::iocshRegister<int, const char*, &dbgl>("dbgl", "level", "pattern");
|
||||
epics::iocshRegister<const char*, &dbLoadGroupWrap>("dbLoadGroup", "jsonfile");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
+241
-207
@@ -7,242 +7,276 @@
|
||||
* 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"
|
||||
|
||||
#include <pv/qsrv.h>
|
||||
|
||||
extern "C" int softIocPVA_registerRecordDeviceDriver(struct dbBase *pdbbase);
|
||||
|
||||
#ifdef __rtems__
|
||||
#define DBD_FILE "dbd/softIocPVA.dbd"
|
||||
#define EXIT_FILE "db/softIocExit.db"
|
||||
#else
|
||||
#define DBD_FILE FINAL_LOCATION "/dbd/softIocPVA.dbd"
|
||||
#define EXIT_FILE FINAL_LOCATION "/db/softIocExit.db"
|
||||
#ifndef EPICS_BASE
|
||||
// so IDEs knows EPICS_BASE is a string constant
|
||||
# define EPICS_BASE "/"
|
||||
# error -DEPICS_BASE required
|
||||
#endif
|
||||
|
||||
#ifdef VERSION_INT
|
||||
#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1)
|
||||
#define USE_EXIT_LATER
|
||||
#if EPICS_VERSION_INT>=VERSION_INT(7,0,2,0)
|
||||
# define USE_EXECDIR
|
||||
#endif
|
||||
#endif
|
||||
const char *arg0;
|
||||
const char *base_dbd = DBD_FILE;
|
||||
const char *exit_db = EXIT_FILE;
|
||||
|
||||
#define DBD_BASE "dbd" OSI_PATH_SEPARATOR "softIocPVA.dbd"
|
||||
#define EXIT_BASE "db" OSI_PATH_SEPARATOR "softIocExit.db"
|
||||
#define DBD_FILE_REL ".." OSI_PATH_SEPARATOR ".." OSI_PATH_SEPARATOR DBD_BASE
|
||||
#define EXIT_FILE_REL ".." OSI_PATH_SEPARATOR ".." OSI_PATH_SEPARATOR EXIT_BASE
|
||||
#define DBD_FILE EPICS_BASE OSI_PATH_SEPARATOR DBD_BASE
|
||||
#define EXIT_FILE EPICS_BASE OSI_PATH_SEPARATOR EXIT_BASE
|
||||
|
||||
namespace {
|
||||
|
||||
bool verbose = false;
|
||||
|
||||
static void exitSubroutine(subRecord *precord) {
|
||||
#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] [-v] [-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"
|
||||
" -v Verbose, display steps taken during startup.\n"
|
||||
"\n"
|
||||
" -a <acf> Access Security configuration file. Macro substitution is\n"
|
||||
" performed.\n"
|
||||
"\n"
|
||||
" -G <json> DB Group definition file in JSON format.\n"
|
||||
"\n"
|
||||
" -m <MAC>=<value>,... Set/replace macro definitions used by subsequent -d and\n"
|
||||
" -a.\n"
|
||||
"\n"
|
||||
" -d <db> Load records from file (dbLoadRecords). Macro substitution is\n"
|
||||
" performed.\n"
|
||||
"\n"
|
||||
" -x <prefix> Load softIocExit.db. Provides a record \"<prefix>:exit\".\n"
|
||||
" Put 0 to exit with success, or non-zero to exit with an error.\n"
|
||||
"\n"
|
||||
"Any number of -m and -d arguments can be interspersed; the macros are applied\n"
|
||||
"to the following .db files. Each later -m option causes earlier macros to be\n"
|
||||
"discarded.\n"
|
||||
"\n"
|
||||
"A st.cmd file is optional. If any databases were loaded the st.cmd file will\n"
|
||||
"be run *after* iocInit. To perform iocsh commands before iocInit, all database\n"
|
||||
"loading must be performed by the script itself, or by the user from the\n"
|
||||
"interactive IOC shell.\n"
|
||||
"\n"
|
||||
"Compiled-in path to softIocPVA.dbd is:\n"
|
||||
"\t"<<base_dbd.c_str()<<"\n";
|
||||
}
|
||||
|
||||
void errIf(int ret, const std::string& msg)
|
||||
{
|
||||
if(ret)
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
bool lazy_dbd_loaded;
|
||||
|
||||
void lazy_dbd(const std::string& dbd_file) {
|
||||
if(lazy_dbd_loaded) return;
|
||||
lazy_dbd_loaded = true;
|
||||
|
||||
if (verbose)
|
||||
std::cout<<"dbLoadDatabase(\""<<dbd_file<<"\")\n";
|
||||
errIf(dbLoadDatabase(dbd_file.c_str(), NULL, NULL),
|
||||
std::string("Failed to load DBD file: ")+dbd_file);
|
||||
|
||||
if (verbose)
|
||||
std::cout<<"softIocPVA_registerRecordDeviceDriver(pdbbase)\n";
|
||||
softIocPVA_registerRecordDeviceDriver(pdbbase);
|
||||
registryFunctionAdd("exit", (REGISTRYFUNCTION) exitSubroutine);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
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;
|
||||
bool ranScript = 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:G:v")) != -1) {
|
||||
switch (opt) {
|
||||
case 'h': /* Print usage */
|
||||
usage(argv[0], dbd_file);
|
||||
epicsExit(0);
|
||||
return 0;
|
||||
default:
|
||||
usage(argv[0], dbd_file);
|
||||
std::cerr<<"Unknown argument: -"<<char(opt)<<"\n";
|
||||
epicsExit(2);
|
||||
return 2;
|
||||
case 'a':
|
||||
lazy_dbd(dbd_file);
|
||||
if (!macros.empty()) {
|
||||
if (verbose)
|
||||
std::cout<<"asSetSubstitutions(\""<<macros<<"\")\n";
|
||||
if(asSetSubstitutions(macros.c_str()))
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
if (verbose)
|
||||
std::cout<<"asSetFilename(\""<<optarg<<"\")\n";
|
||||
if(asSetFilename(optarg))
|
||||
throw std::bad_alloc();
|
||||
break;
|
||||
case 'D':
|
||||
if(lazy_dbd_loaded) {
|
||||
throw std::runtime_error("-D specified too late. softIocPVA.dbd already loaded.\n");
|
||||
}
|
||||
dbd_file = optarg;
|
||||
break;
|
||||
case 'd':
|
||||
lazy_dbd(dbd_file);
|
||||
if (verbose) {
|
||||
std::cout<<"dbLoadRecords(\""<<optarg<<"\"";
|
||||
if(!macros.empty())
|
||||
std::cout<<", \""<<macros<<"\"";
|
||||
std::cout<<")\n";
|
||||
}
|
||||
errIf(dbLoadRecords(optarg, macros.c_str()),
|
||||
std::string("Failed to load: ")+optarg);
|
||||
loadedDb = true;
|
||||
break;
|
||||
case 'm':
|
||||
macros = optarg;
|
||||
break;
|
||||
case 'S':
|
||||
interactive = false;
|
||||
break;
|
||||
case 's':
|
||||
break; // historical
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
case 'x':
|
||||
lazy_dbd(dbd_file);
|
||||
xmacro = "IOC=";
|
||||
xmacro += optarg;
|
||||
errIf(dbLoadRecords(exit_file.c_str(), xmacro.c_str()),
|
||||
std::string("Failed to load: ")+exit_file);
|
||||
loadedDb = true;
|
||||
break;
|
||||
case 'G':
|
||||
dbLoadGroup(optarg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lazy_dbd(dbd_file);
|
||||
|
||||
if(optind<argc) {
|
||||
// run script
|
||||
// ignore any extra positional args (historical)
|
||||
|
||||
if (verbose)
|
||||
std::cout<<"# Begin "<<argv[optind]<<"\n";
|
||||
errIf(iocsh(argv[optind]),
|
||||
std::string("Error in ")+argv[optind]);
|
||||
if (verbose)
|
||||
std::cout<<"# End "<<argv[optind]<<"\n";
|
||||
|
||||
epicsThreadSleep(0.2);
|
||||
ranScript = true; /* Assume the script has done any necessary initialization */
|
||||
}
|
||||
|
||||
if (loadedDb) {
|
||||
if (verbose)
|
||||
std::cout<<"iocInit()\n";
|
||||
iocInit();
|
||||
epicsThreadSleep(0.2);
|
||||
}
|
||||
|
||||
if(interactive) {
|
||||
std::cout.flush();
|
||||
std::cerr.flush();
|
||||
if(iocsh(NULL)) {
|
||||
epicsExit(1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (loadedDb || ranScript) {
|
||||
epicsThreadExitMain();
|
||||
|
||||
} else {
|
||||
usage(argv[0], dbd_file);
|
||||
std::cerr<<"Nothing to do!\n";
|
||||
epicsExit(1);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
epicsExit(0);
|
||||
return 0;
|
||||
|
||||
}catch(std::exception& e){
|
||||
std::cerr<<"Error: "<<e.what()<<"\n";
|
||||
epicsExit(2);
|
||||
return 2;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#include <pv/sharedPtr.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include "helper.h"
|
||||
#include "tpool.h"
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
|
||||
#include <pv/sharedPtr.h>
|
||||
|
||||
#include <shareLib.h>
|
||||
|
||||
struct WorkQueue : private epicsThreadRunable
|
||||
{
|
||||
typedef std::tr1::weak_ptr<epicsThreadRunable> value_type;
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ testpdb_SRCS += p2pTestIoc_registerRecordDeviceDriver.cpp
|
||||
testpdb_LIBS += qsrv
|
||||
TESTS += testpdb
|
||||
|
||||
PROD_HOST += check_consist
|
||||
TESTPROD_HOST += check_consist
|
||||
check_consist_SRCS += check_consist.cpp
|
||||
|
||||
ifdef BASE_3_16
|
||||
|
||||
@@ -230,7 +230,7 @@ void testDBR2PVD_array()
|
||||
|
||||
MAIN(testdbf_copy)
|
||||
{
|
||||
testPlan(51);
|
||||
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);
|
||||
@@ -238,6 +238,9 @@ MAIN(testdbf_copy)
|
||||
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);
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
record (waveform, "TEST") {
|
||||
field(FTVL, "SHORT")
|
||||
field(NELM, "10")
|
||||
}
|
||||
@@ -18,6 +18,7 @@ void test_parse()
|
||||
" \"+atomic\":false,\n"
|
||||
" \"fld\":{\n"
|
||||
" \"+type\": \"simple\","
|
||||
" \"+channel\": \"VAL\","
|
||||
" \"+putorder\": -4"
|
||||
" },\n"
|
||||
" \"\":{\n"
|
||||
@@ -29,7 +30,7 @@ void test_parse()
|
||||
"}";
|
||||
|
||||
GroupConfig conf;
|
||||
GroupConfig::parse(txt, conf);
|
||||
GroupConfig::parse(txt, "rec", conf);
|
||||
|
||||
testOk(conf.warning.empty(), "Warnings: %s", conf.warning.c_str());
|
||||
|
||||
@@ -37,7 +38,7 @@ void test_parse()
|
||||
testOk1(conf.groups["grpa"].atomic_set);
|
||||
|
||||
testEqual(conf.groups["grpa"].fields["fld"].type, "simple");
|
||||
testEqual(conf.groups["grpa"].fields["fld"].channel, "");
|
||||
testEqual(conf.groups["grpa"].fields["fld"].channel, "rec.VAL");
|
||||
testEqual(conf.groups["grpa"].fields["fld"].putorder, -4);
|
||||
|
||||
testEqual(conf.groups["grpa"].fields[""].type, "top");
|
||||
@@ -49,15 +50,15 @@ void test_fail()
|
||||
|
||||
{
|
||||
GroupConfig conf;
|
||||
testThrows(std::runtime_error, GroupConfig::parse("{", conf));
|
||||
testThrows(std::runtime_error, GroupConfig::parse("{", "", conf));
|
||||
}
|
||||
{
|
||||
GroupConfig conf;
|
||||
testThrows(std::runtime_error, GroupConfig::parse("{\"G\":{\"F\":{\"K\":{}}}}", conf));
|
||||
testThrows(std::runtime_error, GroupConfig::parse("{\"G\":{\"F\":{\"K\":{}}}}", "", conf));
|
||||
}
|
||||
{
|
||||
GroupConfig conf;
|
||||
testThrows(std::runtime_error, GroupConfig::parse("{\"G\":5}", conf));
|
||||
testThrows(std::runtime_error, GroupConfig::parse("{\"G\":5}", "", conf));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+42
-1
@@ -292,6 +292,45 @@ void testGroupMonitorTriggers(pvac::ClientProvider& client)
|
||||
#endif
|
||||
}
|
||||
|
||||
void testFilters(pvac::ClientProvider& client)
|
||||
{
|
||||
testDiag("test w/ server side filters");
|
||||
|
||||
pvd::shared_vector<const pvd::int16> expected;
|
||||
{
|
||||
pvd::shared_vector<pvd::int16> scratch(9);
|
||||
scratch[0] = 9;
|
||||
scratch[1] = 8;
|
||||
scratch[2] = 7;
|
||||
scratch[3] = 6;
|
||||
scratch[4] = 5;
|
||||
scratch[5] = 4;
|
||||
scratch[6] = 3;
|
||||
scratch[7] = 2;
|
||||
scratch[8] = 1;
|
||||
expected = pvd::freeze(scratch);
|
||||
}
|
||||
|
||||
client.connect("TEST").put().set("value", expected).exec();
|
||||
|
||||
pvd::PVStructure::const_shared_pointer root(client.connect("TEST").get());
|
||||
|
||||
testFieldEqual<pvd::PVShortArray>(root, "value", expected);
|
||||
|
||||
root = client.connect("TEST.{\"arr\":{\"i\":2}}").get();
|
||||
{
|
||||
pvd::shared_vector<pvd::int16> scratch(5);
|
||||
scratch[0] = 9;
|
||||
scratch[1] = 7;
|
||||
scratch[2] = 5;
|
||||
scratch[3] = 3;
|
||||
scratch[4] = 1;
|
||||
expected = pvd::freeze(scratch);
|
||||
}
|
||||
|
||||
testFieldEqual<pvd::PVShortArray>(root, "value", expected);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C"
|
||||
@@ -299,7 +338,7 @@ void p2pTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
MAIN(testpdb)
|
||||
{
|
||||
testPlan(93);
|
||||
testPlan(95);
|
||||
try{
|
||||
QSRVRegistrar_counters();
|
||||
epics::RefSnapshot ref_before;
|
||||
@@ -318,6 +357,7 @@ MAIN(testpdb)
|
||||
#ifdef USE_MULTILOCK
|
||||
testdbReadDatabase("testpdb-groups.db", NULL, NULL);
|
||||
#endif
|
||||
testdbReadDatabase("testfilters.db", NULL, NULL);
|
||||
|
||||
IOC.init();
|
||||
|
||||
@@ -333,6 +373,7 @@ MAIN(testpdb)
|
||||
testSingleMonitor(client);
|
||||
testGroupMonitor(client);
|
||||
testGroupMonitorTriggers(client);
|
||||
testFilters(client);
|
||||
|
||||
testEqual(epics::atomic::get(PDBProvider::num_instances), 1u);
|
||||
}
|
||||
|
||||
+25
-25
@@ -1,8 +1,8 @@
|
||||
|
||||
#include <dbUnitTest.h>
|
||||
#include <testMain.h>
|
||||
#include <longinRecord.h>
|
||||
#include <longoutRecord.h>
|
||||
#include <int64inRecord.h>
|
||||
#include <int64outRecord.h>
|
||||
|
||||
#include <pv/qsrv.h>
|
||||
#include "utilities.h"
|
||||
@@ -15,50 +15,50 @@ void testGet()
|
||||
{
|
||||
testDiag("==== testGet ====");
|
||||
|
||||
longinRecord *li1 = (longinRecord*)testdbRecordPtr("src:li1");
|
||||
int64inRecord *i1 = (int64inRecord*)testdbRecordPtr("src:i1");
|
||||
|
||||
while(!dbIsLinkConnected(&li1->inp))
|
||||
testqsrvWaitForLinkEvent(&li1->inp);
|
||||
while(!dbIsLinkConnected(&i1->inp))
|
||||
testqsrvWaitForLinkEvent(&i1->inp);
|
||||
|
||||
testdbGetFieldEqual("target:li.VAL", DBF_LONG, 42);
|
||||
testdbGetFieldEqual("target:i.VAL", DBF_INT64, 42LL);
|
||||
|
||||
testdbGetFieldEqual("src:li1.VAL", DBF_LONG, 0); // value before first process
|
||||
testdbGetFieldEqual("src:i1.VAL", DBF_INT64, 0LL); // value before first process
|
||||
|
||||
testdbGetFieldEqual("src:li1.INP", DBF_STRING, "{\"pva\":\"target:li\"}");
|
||||
testdbGetFieldEqual("src:i1.INP", DBF_STRING, "{\"pva\":\"target:i\"}");
|
||||
|
||||
testdbPutFieldOk("src:li1.PROC", DBF_LONG, 1);
|
||||
testdbPutFieldOk("src:i1.PROC", DBF_INT64, 1LL);
|
||||
|
||||
testdbGetFieldEqual("src:li1.VAL", DBF_LONG, 42);
|
||||
testdbGetFieldEqual("src:i1.VAL", DBF_INT64, 42LL);
|
||||
|
||||
testdbPutFieldOk("src:li1.INP", DBF_STRING, "{\"pva\":\"target:ai\"}");
|
||||
testdbPutFieldOk("src:i1.INP", DBF_STRING, "{\"pva\":\"target:ai\"}");
|
||||
|
||||
while(!dbIsLinkConnected(&li1->inp))
|
||||
testqsrvWaitForLinkEvent(&li1->inp);
|
||||
while(!dbIsLinkConnected(&i1->inp))
|
||||
testqsrvWaitForLinkEvent(&i1->inp);
|
||||
|
||||
testdbGetFieldEqual("src:li1.VAL", DBF_LONG, 42); // changing link doesn't automatically process
|
||||
testdbGetFieldEqual("src:i1.VAL", DBF_INT64, 42LL); // changing link doesn't automatically process
|
||||
|
||||
testdbPutFieldOk("src:li1.PROC", DBF_LONG, 1);
|
||||
testdbPutFieldOk("src:i1.PROC", DBF_INT64, 1LL);
|
||||
|
||||
testdbGetFieldEqual("src:li1.VAL", DBF_LONG, 4); // now it's changed
|
||||
testdbGetFieldEqual("src:i1.VAL", DBF_INT64, 4LL); // now it's changed
|
||||
}
|
||||
|
||||
void testPut()
|
||||
{
|
||||
testDiag("==== testPut ====");
|
||||
|
||||
longoutRecord *lo2 = (longoutRecord*)testdbRecordPtr("src:lo2");
|
||||
int64outRecord *o2 = (int64outRecord*)testdbRecordPtr("src:o2");
|
||||
|
||||
while(!dbIsLinkConnected(&lo2->out))
|
||||
testqsrvWaitForLinkEvent(&lo2->out);
|
||||
while(!dbIsLinkConnected(&o2->out))
|
||||
testqsrvWaitForLinkEvent(&o2->out);
|
||||
|
||||
testdbGetFieldEqual("target:li2.VAL", DBF_LONG, 43);
|
||||
testdbGetFieldEqual("src:lo2.VAL", DBF_LONG, 0);
|
||||
testdbGetFieldEqual("src:lo2.OUT", DBF_STRING, "{\"pva\":\"target:li2\"}");
|
||||
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:lo2.VAL", DBF_LONG, 14);
|
||||
testdbPutFieldOk("src:o2.VAL", DBF_INT64, 14LL);
|
||||
|
||||
testdbGetFieldEqual("target:li2.VAL", DBF_LONG, 14);
|
||||
testdbGetFieldEqual("src:lo2.VAL", DBF_LONG, 14);
|
||||
testdbGetFieldEqual("target:i2.VAL", DBF_INT64, 14LL);
|
||||
testdbGetFieldEqual("src:o2.VAL", DBF_INT64, 14LL);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -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:lo2") {
|
||||
field(OUT, {pva:"target:li2"})
|
||||
record(int64out, "src:o2") {
|
||||
field(OUT, {"pva":"target:i2"})
|
||||
}
|
||||
|
||||
+260
-52
@@ -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,30 +70,62 @@ 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::PVStructurePtr root;
|
||||
p2p::auto_ptr<PVIF> pvif_li;
|
||||
#ifdef USE_INT64
|
||||
p2p::auto_ptr<PVIF> pvif_i64;
|
||||
#endif
|
||||
p2p::auto_ptr<PVIF> pvif_si;
|
||||
p2p::auto_ptr<PVIF> pvif_ai;
|
||||
p2p::auto_ptr<PVIF> pvif_ai_rval;
|
||||
p2p::auto_ptr<PVIF> pvif_mbbi;
|
||||
{
|
||||
ScalarBuilder builder_li(chan_li);
|
||||
#ifdef USE_INT64
|
||||
ScalarBuilder builder_i64(chan_i64);
|
||||
#endif
|
||||
ScalarBuilder builder_si(chan_si);
|
||||
ScalarBuilder builder_ai(chan_ai);
|
||||
ScalarBuilder builder_ai_rval(chan_ai_rval);
|
||||
ScalarBuilder builder_mbbi(chan_mbbi);
|
||||
|
||||
pvd::FieldConstPtr dtype_li(builder.dtype(chan_li));
|
||||
pvd::FieldConstPtr dtype_si(builder.dtype(chan_si));
|
||||
pvd::FieldConstPtr dtype_ai(builder.dtype(chan_ai));
|
||||
pvd::FieldConstPtr dtype_ai_rval(builder.dtype(chan_ai_rval));
|
||||
pvd::FieldConstPtr dtype_mbbi(builder.dtype(chan_mbbi));
|
||||
pvd::FieldConstPtr dtype_li(builder_li.dtype());
|
||||
#ifdef USE_INT64
|
||||
pvd::FieldConstPtr dtype_i64(builder_i64.dtype());
|
||||
#endif
|
||||
pvd::FieldConstPtr dtype_si(builder_si.dtype());
|
||||
pvd::FieldConstPtr dtype_ai(builder_ai.dtype());
|
||||
pvd::FieldConstPtr dtype_ai_rval(builder_ai_rval.dtype());
|
||||
pvd::FieldConstPtr dtype_mbbi(builder_mbbi.dtype());
|
||||
|
||||
pvd::StructureConstPtr dtype_root(pvd::getFieldCreate()->createFieldBuilder()
|
||||
->add("li", dtype_li)
|
||||
->add("si", dtype_si)
|
||||
->add("ai", dtype_ai)
|
||||
->add("ai_rval", dtype_ai_rval)
|
||||
->add("mbbi", dtype_mbbi)
|
||||
->createStructure());
|
||||
pvd::StructureConstPtr dtype_root(pvd::getFieldCreate()->createFieldBuilder()
|
||||
->add("li", dtype_li)
|
||||
#ifdef USE_INT64
|
||||
->add("i64", dtype_i64)
|
||||
#endif
|
||||
->add("si", dtype_si)
|
||||
->add("ai", dtype_ai)
|
||||
->add("ai_rval", dtype_ai_rval)
|
||||
->add("mbbi", dtype_mbbi)
|
||||
->createStructure());
|
||||
|
||||
pvd::PVStructurePtr root(pvd::getPVDataCreate()->createPVStructure(dtype_root));
|
||||
root = pvd::getPVDataCreate()->createPVStructure(dtype_root);
|
||||
|
||||
p2p::auto_ptr<PVIF> pvif_li(builder.attach(chan_li, root, FieldName("li")));
|
||||
p2p::auto_ptr<PVIF> pvif_si(builder.attach(chan_si, root, FieldName("si")));
|
||||
p2p::auto_ptr<PVIF> pvif_ai(builder.attach(chan_ai, root, FieldName("ai")));
|
||||
p2p::auto_ptr<PVIF> pvif_ai_rval(builder.attach(chan_ai_rval, root, FieldName("ai_rval")));
|
||||
p2p::auto_ptr<PVIF> pvif_mbbi(builder.attach(chan_mbbi, root, FieldName("mbbi")));
|
||||
pvif_li.reset(builder_li.attach(root, FieldName("li")));
|
||||
#ifdef USE_INT64
|
||||
pvif_i64.reset(builder_i64.attach(root, FieldName("i64")));
|
||||
#endif
|
||||
pvif_si.reset(builder_si.attach(root, FieldName("si")));
|
||||
pvif_ai.reset(builder_ai.attach(root, FieldName("ai")));
|
||||
pvif_ai_rval.reset(builder_ai_rval.attach(root, FieldName("ai_rval")));
|
||||
pvif_mbbi.reset(builder_mbbi.attach(root, FieldName("mbbi")));
|
||||
}
|
||||
|
||||
testShow()<<"Entire structure\n"<<root;
|
||||
|
||||
pvd::BitSet mask;
|
||||
|
||||
@@ -94,21 +143,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 +211,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 +239,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 +258,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 +287,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 +300,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 +329,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 +340,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 +358,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 +373,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());
|
||||
@@ -313,30 +442,37 @@ void testPlain()
|
||||
DBCH chan_ai("test:ai");
|
||||
DBCH chan_mbbi("test:mbbi");
|
||||
|
||||
p2p::auto_ptr<PVIFBuilder> builder;
|
||||
pvd::PVStructurePtr root;
|
||||
p2p::auto_ptr<PVIF> pvif_li;
|
||||
p2p::auto_ptr<PVIF> pvif_si;
|
||||
p2p::auto_ptr<PVIF> pvif_ai;
|
||||
p2p::auto_ptr<PVIF> pvif_mbbi;
|
||||
{
|
||||
builder.reset(PVIFBuilder::create("plain"));
|
||||
p2p::auto_ptr<PVIFBuilder> builder_li(PVIFBuilder::create("plain", chan_li));
|
||||
p2p::auto_ptr<PVIFBuilder> builder_si(PVIFBuilder::create("plain", chan_si));
|
||||
p2p::auto_ptr<PVIFBuilder> builder_ai(PVIFBuilder::create("plain", chan_ai));
|
||||
p2p::auto_ptr<PVIFBuilder> builder_mbbi(PVIFBuilder::create("plain", chan_mbbi));
|
||||
|
||||
pvd::FieldConstPtr dtype_li(builder_li->dtype());
|
||||
pvd::FieldConstPtr dtype_si(builder_si->dtype());
|
||||
pvd::FieldConstPtr dtype_ai(builder_ai->dtype());
|
||||
pvd::FieldConstPtr dtype_mbbi(builder_mbbi->dtype());
|
||||
|
||||
pvd::StructureConstPtr dtype_root(pvd::getFieldCreate()->createFieldBuilder()
|
||||
->add("li", dtype_li)
|
||||
->add("si", dtype_si)
|
||||
->add("ai", dtype_ai)
|
||||
->add("mbbi", dtype_mbbi)
|
||||
->createStructure());
|
||||
|
||||
root = pvd::getPVDataCreate()->createPVStructure(dtype_root);
|
||||
|
||||
pvif_li.reset(builder_li->attach(root, FieldName("li")));
|
||||
pvif_si.reset(builder_si->attach(root, FieldName("si")));
|
||||
pvif_ai.reset(builder_ai->attach(root, FieldName("ai")));
|
||||
pvif_mbbi.reset(builder_mbbi->attach(root, FieldName("mbbi")));
|
||||
}
|
||||
|
||||
pvd::FieldConstPtr dtype_li(builder->dtype(chan_li));
|
||||
pvd::FieldConstPtr dtype_si(builder->dtype(chan_si));
|
||||
pvd::FieldConstPtr dtype_ai(builder->dtype(chan_ai));
|
||||
pvd::FieldConstPtr dtype_mbbi(builder->dtype(chan_mbbi));
|
||||
|
||||
pvd::StructureConstPtr dtype_root(pvd::getFieldCreate()->createFieldBuilder()
|
||||
->add("li", dtype_li)
|
||||
->add("si", dtype_si)
|
||||
->add("ai", dtype_ai)
|
||||
->add("mbbi", dtype_mbbi)
|
||||
->createStructure());
|
||||
|
||||
pvd::PVStructurePtr root(pvd::getPVDataCreate()->createPVStructure(dtype_root));
|
||||
|
||||
p2p::auto_ptr<PVIF> pvif_li(builder->attach(chan_li, root, FieldName("li")));
|
||||
p2p::auto_ptr<PVIF> pvif_si(builder->attach(chan_si, root, FieldName("si")));
|
||||
p2p::auto_ptr<PVIF> pvif_ai(builder->attach(chan_ai, root, FieldName("ai")));
|
||||
p2p::auto_ptr<PVIF> pvif_mbbi(builder->attach(chan_mbbi, root, FieldName("mbbi")));
|
||||
|
||||
pvd::BitSet mask;
|
||||
|
||||
mask.clear();
|
||||
@@ -410,12 +546,84 @@ void testPlain()
|
||||
dbScanUnlock((dbCommon*)prec_mbbi);
|
||||
}
|
||||
|
||||
void testFilters()
|
||||
{
|
||||
testDiag("testFilter");
|
||||
|
||||
#if EPICS_VERSION_INT < VERSION_INT(7, 0, 0, 0)
|
||||
testSkip(5, "Needs Base >=7.0");
|
||||
#else
|
||||
|
||||
TestIOC IOC;
|
||||
|
||||
testdbReadDatabase("p2pTestIoc.dbd", NULL, NULL);
|
||||
p2pTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("testfilters.db", NULL, NULL);
|
||||
|
||||
IOC.init();
|
||||
|
||||
static const epicsInt32 arr[] = {9, 8, 7, 6, 5, 4, 3, 2, 1};
|
||||
testdbPutArrFieldOk("TEST", DBF_LONG, 9, arr);
|
||||
|
||||
#if EPICS_VERSION_INT > VERSION_INT(7, 0, 5, 0)
|
||||
testdbGetArrFieldEqual("TEST", DBF_LONG, 10, 9, arr);
|
||||
testdbGetArrFieldEqual("TEST.{\"arr\":{\"s\":5}}", DBF_LONG, 10, 4, arr+5);
|
||||
|
||||
static const epicsInt32 arr2[] = {9, 7, 5, 3, 1};
|
||||
testdbGetArrFieldEqual("TEST.{\"arr\":{\"i\":2}}", DBF_LONG, 10, 5, arr2);
|
||||
|
||||
#else
|
||||
testSkip(3, "dbUnitTest doesn't use dbChannel");
|
||||
#endif
|
||||
|
||||
pvd::PVStructurePtr root;
|
||||
p2p::auto_ptr<PVIF> pvif;
|
||||
|
||||
DBCH chan("TEST.{\"arr\":{\"i\":2}}");
|
||||
ScalarBuilder builder(chan);
|
||||
|
||||
root = pvd::FieldBuilder::begin()
|
||||
->add("dut", builder.dtype())
|
||||
->createStructure()->build();
|
||||
|
||||
pvif.reset(builder.attach(root, FieldName("dut")));
|
||||
|
||||
LocalFL fl(0, chan.chan);
|
||||
|
||||
pvd::shared_vector<pvd::int16> scratch(5);
|
||||
scratch[0] = 9;
|
||||
scratch[1] = 7;
|
||||
scratch[2] = 5;
|
||||
scratch[3] = 3;
|
||||
scratch[4] = 1;
|
||||
pvd::shared_vector<const pvd::int16> expected(pvd::freeze(scratch));
|
||||
|
||||
dbCommon *prec = testdbRecordPtr("TEST");
|
||||
dbScanLock(prec);
|
||||
pvd::BitSet changed;
|
||||
pvif->put(changed, DBE_VALUE, fl.pfl);
|
||||
dbScanUnlock(prec);
|
||||
|
||||
testFieldEqual<pvd::PVShortArray>(root, "dut.value", expected);
|
||||
#endif // >= 7.0
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MAIN(testpvif)
|
||||
{
|
||||
testPlan(71);
|
||||
testPlan(80
|
||||
#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();
|
||||
testFilters();
|
||||
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