141 Commits

Author SHA1 Message Date
Michael Davidsaver 466d41ebb9 1.3.1 2021-06-30 10:05:36 -07:00
Michael Davidsaver d18e2219b3 pvalink: missing NULL check 2021-04-23 15:29:45 -07:00
Michael Davidsaver ff22538129 pdb: Create db_field_log for GETs 2021-03-17 13:22:42 -07:00
Michael Davidsaver 67e668795e test PDB get w/ filter 2021-03-17 13:22:42 -07:00
Michael Davidsaver 1ac2e6c809 ci: github actions add mingw 2021-03-17 12:47:34 -07:00
Michael Davidsaver 40b327cfcb testpvif 3.15 compat 2021-03-12 11:45:13 -08:00
Michael Davidsaver 20edb8fdf5 CI update 2021-03-12 11:09:26 -08:00
Michael Davidsaver babda345b4 ci: appveyor fix mingw builds 2021-03-12 09:59:02 -08:00
Michael Davidsaver 04fbaf5a2e fix LocalFL 2021-03-12 09:59:02 -08:00
Michael Davidsaver 3438fde276 test: cover server side filters 2021-03-12 09:59:02 -08:00
Simon Rose b515fd30ca Syncing softMain.cpp with epics-base 2021-03-12 07:09:22 -08:00
Michael Davidsaver ad8b77e19f Set next development version 2021-02-26 13:26:20 -08:00
Michael Davidsaver b8389ac6a1 1.3.0 2021-02-26 13:24:26 -08:00
Andrew Johnson d47008ee53 Update GHA config
Better job names
Upload .tap files as artifacts
2021-01-05 22:12:33 -06:00
Andrew Johnson a709854f0d Update .appveyor.yml for newer ci-tools
Also set the correct notification email address
2021-01-05 22:12:33 -06:00
Andrew Johnson 527afaf856 Merge pull request #35 from anjohnson/master
About to merge my yajl5 branch to 7.0
2021-01-05 21:37:48 -06:00
Michael Davidsaver d65af720d1 doc 2020-11-28 10:45:34 -08:00
Michael Davidsaver 503cf414be 3.15 compat 2020-11-28 10:45:34 -08:00
Michael Davidsaver 1682c991d2 use dbChannel* in builder 2020-11-28 10:45:34 -08:00
Michael Davidsaver c68c0038e6 store dbChannel* in builder 2020-11-28 10:45:34 -08:00
Michael Davidsaver b69c25feb6 pass dbChannel* to PVIFBuilder::create() 2020-11-28 10:45:34 -08:00
Michael Davidsaver eae493e732 delay PVIFBuilder::create 2020-11-28 10:45:34 -08:00
Michael Davidsaver 29d00e7e38 simplify 2020-11-28 10:45:34 -08:00
Michael Davidsaver 1335d75403 add dbLoadGroup() 2020-11-28 10:45:34 -08:00
Michael Davidsaver c32cc1ba3c combine group info() processing 2020-11-28 10:44:32 -08:00
Michael Davidsaver f5278ef4ac groups: move record name prepend into parser hooks
Also, accumulate config instead of replacing
2020-11-28 10:44:32 -08:00
Michael Davidsaver b9d3ad8f90 ci: travis-ci -> github actions 2020-11-28 10:44:32 -08:00
Andrew Johnson 0e772037ee Add -v (verbose) flag to softIocPVA
Only display the startup steps with -v
Print each step *before* running it, so any error messages follow.
2020-10-28 23:28:08 -05:00
Michael Davidsaver d100eac09e update README 2020-08-30 09:29:54 -07:00
Andrew Johnson 740aad6712 Modify test database to avoid break when json5 merged
No other changes will be needed when merging the json5 branch into
Base-7.0, these changes let testpvalink pass with and without it.
2020-08-09 00:55:51 -05:00
Michael Davidsaver c66f224602 Set next development version 2020-07-26 13:43:02 -07:00
Michael Davidsaver b1d28cca39 Update version numbers for release 2020-07-26 13:41:51 -07:00
Michael Davidsaver 50be5b6025 release notes 2020-07-11 14:08:26 -07:00
Michael Davidsaver 8e7a1ad295 checkDISP 2020-07-05 23:06:30 -07:00
Michael Davidsaver e894ae753f create second dbChannel for DBE_PROPERTY for groups 2020-07-05 23:06:30 -07:00
Michael Davidsaver 4559f483bf create second dbChannel for DBE_PROPERTY
Create second channel, and second set of filter states,
for subscription to DBE_PROPERTY.  Accommodate filters
which drop events.
2020-07-02 22:45:58 -07:00
Michael Davidsaver e843db5d27 softMain compat 2020-06-01 19:54:08 -07:00
Michael Davidsaver 8363c870bd adapt softMain as softIocPVA 2020-06-01 19:54:08 -07:00
Michael Davidsaver 9499137bb4 update softMain.cpp from Base circa 7.0.4 2020-06-01 19:54:08 -07:00
Michael Davidsaver c4798dc2bc shareLib.h cleanup 2020-06-01 18:22:08 -07:00
Michael Davidsaver 11eeed9d0c notes 2020-06-01 18:22:08 -07:00
Andrew Johnson 94d1eedc75 Set next development version 2020-05-28 16:26:01 -05:00
Andrew Johnson cb13435d15 Update version numbers for release 2020-05-28 15:43:34 -05:00
Michael Davidsaver cda2222ed5 p2p: deprecation notice 2020-05-19 17:06:28 -07:00
Michael Davidsaver 68708ff530 p2p: Fix EPICS_PVA*_AUTO_ADDR_LIST 2020-05-19 16:48:53 -07:00
Michael Davidsaver 137ecfed56 ci-scripts 2.3.2 (with appveyor) 2020-04-23 15:16:41 -07:00
Michael Davidsaver 29a6f261dc travis use ci-scripts 2019-12-20 12:06:11 -08:00
Michael Davidsaver 1d9fbbea0b don't install check_consist 2019-12-19 18:45:38 -08:00
Michael Davidsaver 536f4dd02f finalize doxygen 2019-11-03 20:08:49 -08:00
Andrew Johnson 21ae754869 Incr version and set development flag after release 2019-10-31 18:00:34 -05:00
Andrew Johnson 3024f9fb0c Clear development flag for 1.2.2 release 2019-10-31 17:58:09 -05:00
Andrew Johnson 58a2159969 Indent sub-bullets by 2 spaces for pandoc gfm 2019-10-31 17:55:59 -05:00
Michael Davidsaver c200dd22f1 doc for 1.2.2 2019-10-31 10:55:12 -07:00
Michael Davidsaver 9c6fb5c539 more WIN32 include order games 2019-09-26 13:46:26 -07:00
Freddie Akeroyd 54a882d844 MSVC is picky about vector access 2019-09-25 13:47:03 -07:00
Andrew Johnson c7a54de469 Move include of osiSock.h below db headers for Windows 2019-09-07 09:43:11 +02:00
Michael Davidsaver c2ee7c9dc4 m_data removal
Try to avoid stepping on this vxWorks landmine.
2019-09-06 14:57:03 -07:00
Ralph Lange 3e59a4b6a8 rtd-ci: add read-the-docs integration 2019-09-06 15:42:44 +02:00
Michael Davidsaver 0e0400022c vx const fixup
std::set iteration really should always be const
as mutation would invalidate the ordering.
2019-09-05 20:02:46 -07:00
Michael Davidsaver bdbf57350b replace shareLib.h with QSRV_API
and hopefully avoid at least some of the maddening
include order bugs so common with sharedLib.h.
2019-09-05 20:01:56 -07:00
Michael Davidsaver 2e58e54e1d Merge branch 'auth'
* auth:
  minor
  re-add missing enum_t
  asLib check against PeerInfo
2019-08-31 19:22:23 -07:00
Michael Davidsaver ddfd746c62 minor 2019-08-31 19:18:56 -07:00
Michael Davidsaver e41269230c re-add missing enum_t 2019-08-13 20:08:40 -07:00
Michael Davidsaver c3b6fc08b8 re-add missing enum_t 2019-08-13 20:07:26 -07:00
Andrew Johnson d70a2ff8c3 Update version number to 1.2.2 DEVELOPMENT 2019-08-13 11:08:34 -05:00
Michael Davidsaver ce39c93201 release notes for 1.2.1 2019-07-24 17:42:17 -07:00
Michael Davidsaver d7314eaef4 asLib check against PeerInfo
Check Put permissions
2019-05-16 18:19:38 -07:00
Michael Davidsaver 5170c2230d missing 'else'
doesn't actually change logic flow, but wasn't intentional.
2019-05-13 14:09:38 -07:00
Andrew Johnson 3ed88f2559 testpvalink: Provide expected values as 64-bit integers 2019-05-10 15:02:08 -05:00
Andrew Johnson 2c533d79ab Finish off conversion of testpvalink to int64 record types 2019-05-09 18:05:24 -05:00
Michael Davidsaver a72bc31a44 fix release doc 2019-03-20 14:58:47 -07:00
Michael Davidsaver 521154fd52 1.2.0 2019-03-18 11:44:01 -07:00
Andrew Johnson 6d1cbd87fd Make a local copy of softIocExit.db for standalone builds 2019-03-12 10:52:50 -05:00
Michael Davidsaver 68904852b9 update release notes 2019-03-11 14:51:39 -07:00
Michael Davidsaver a3dc712174 avoid unnecessary copy
dev. leftovers...
2019-03-11 14:47:42 -07:00
Michael Davidsaver 1ce449d40d parse unknown format as number
forward compatibility
2019-01-15 19:15:21 -08:00
Michael Davidsaver 67b384bb6c doc Q:form 2019-01-15 19:15:21 -08:00
Michael Davidsaver f3d7d7cd8f change display.format, add display.form and .precision
Replace printf-style format string with enum + integer.

ScalarBuilder explode scalar()/scalarArray()
2019-01-15 18:48:18 -08:00
Michael Davidsaver 7bc5cbf957 fix int64 in pva link 2019-01-08 10:27:57 -08:00
Michael Davidsaver 0ab4f937a7 test 64-bit integer field access 2019-01-08 09:31:51 -08:00
Michael Davidsaver 3c5a5c805c fix 64-bit integer field access 2019-01-08 09:10:10 -08:00
Michael Davidsaver c79074e7fa doc 2018-12-23 09:26:28 -08:00
Andrew Johnson a245adb5cf Fix symbol exports for Windows builds 2018-11-28 16:07:11 -06:00
Michael Davidsaver c1cec998a9 1.1.0 2018-11-26 12:13:28 -08:00
Michael Davidsaver c80a88c112 rename doxygen to .dox 2018-11-26 12:10:38 -08:00
Michael Davidsaver 1d689d7e96 update travis-ci 2018-10-29 14:15:28 -07:00
Michael Davidsaver 95b23fc6bc minor 2018-10-04 21:07:10 -07:00
Michael Davidsaver afead32e9d p2p more config 2018-10-04 21:07:10 -07:00
Michael Davidsaver db3fdf54e9 allow setting p2pReadOnly from config file 2018-10-03 14:45:58 -07:00
Andrew Johnson 5f434aec59 Add Makefile rule for qsrv.dbd.d to avoid spurious build warning
Prevents "dbdExpand.pl: No input files for ../O.Common/qsrv.dbd"
2018-07-15 08:34:51 -07:00
Michael Davidsaver a4f35714a0 placate MSVC, take 2 2018-06-27 15:46:10 -07:00
Michael Davidsaver dfd28f5474 attempt to placate MSVC
Apparently suffer from template cleverness overload
2018-06-27 15:23:18 -07:00
Michael Davidsaver 0d69aab6e9 fix README 2018-06-20 15:35:15 -07:00
Michael Davidsaver b157d73c0b more dllimport/export 2018-06-10 10:15:33 -05:00
Michael Davidsaver b215396225 drop extraneous epicsExport.h 2018-06-10 09:59:17 -05:00
Michael Davidsaver 17a3324cc8 pvalink: dllimport/export 2018-06-06 15:24:44 -07:00
Michael Davidsaver da3ecc08b4 pvalink: delay QSRV instanciation
Must be delayed until after dbChannelInit(),
but still want to allocate pvaGlobal prior
to link init.  Unfortunately, no initHook
between these steps.  So split initialization
further.
2018-06-05 08:13:42 -07:00
Michael Davidsaver c126f7a1a2 fixups 2018-05-28 10:17:56 -07:00
Michael Davidsaver f86d61274a pvif: used alias'd record name 2018-05-28 09:29:48 -07:00
Michael Davidsaver 9ac4431d6c update release notes 2018-05-28 09:18:44 -07:00
Michael Davidsaver 365e5061af Merge remote-tracking branch 'md/master'
* md/master: (40 commits)
  pvalink: test FWD_LINK
  QSRV: fix put w/ proc w/o values
  pvalink: add wf test case
  pvalink: defer opening links until IOC running
  update iocpvalink to test output links
  pvalink overhaul debug print controls
  pvalink misc
  pvalink minor
  add iocsh dbpvar()
  pvalink: hide QSRV channel create errors
  attempt to disable pvalink for Base <3.16
  pvalink: input proc if changed
  iocpvalink
  pvalink fix sevr and doc
  pvalink local:
  pvalink add retry: option
  pvalink Put use fieldName
  fix tpool cleanup
  testpvalink avoid false positive races
  pvalink config # of workers
  ...
2018-05-28 09:08:12 -07:00
Michael Davidsaver ec05be4e2f pvalink: test FWD_LINK 2018-05-28 08:10:42 -07:00
Michael Davidsaver 76c6473b6f QSRV: fix put w/ proc w/o values
A put with forced processing, but no values
will cause all mappings to process.
2018-05-28 08:10:21 -07:00
Michael Davidsaver 3338d1035d pvalink: add wf test case 2018-05-28 07:55:10 -07:00
Michael Davidsaver b3ffc49f71 pvalink: defer opening links until IOC running 2018-05-28 07:49:59 -07:00
Michael Davidsaver ff67302347 update iocpvalink to test output links 2018-04-25 19:29:11 -07:00
Michael Davidsaver b18777e8fe pvalink overhaul debug print controls 2018-04-25 19:26:03 -07:00
Michael Davidsaver 60699c48c2 pvalink misc 2018-04-25 15:09:21 -07:00
Michael Davidsaver 99ca9b958e pvalink minor 2018-04-25 15:09:01 -07:00
Michael Davidsaver 9839116c56 add iocsh dbpvar() 2018-04-25 14:07:26 -07:00
Michael Davidsaver 40e407de8c pvalink: hide QSRV channel create errors 2018-04-25 10:42:44 -07:00
Michael Davidsaver a22f6b0801 attempt to disable pvalink for Base <3.16
Seems to work, but prints messages showing that there
is still some problem, probably with a ".dep".
Of course these are silent rules, so I have no idea
which one!

> dbdExpand.pl: No input files for ../O.Common/qsrv.dbd
2018-04-23 18:02:50 -07:00
Michael Davidsaver e3c0703df1 pvalink: input proc if changed 2018-04-23 08:08:55 -07:00
Michael Davidsaver 75efc13244 iocpvalink 2018-04-21 10:19:38 -07:00
Michael Davidsaver f3622c0d42 pvalink fix sevr and doc 2018-04-20 16:35:44 -07:00
Michael Davidsaver 3a5ac39627 pvalink local: 2018-04-20 15:57:59 -07:00
Michael Davidsaver e70aa0897d pvalink add retry: option
allow put to be queued on a disconnected channel
2018-04-19 15:21:02 -07:00
Michael Davidsaver af761958e5 pvalink Put use fieldName 2018-04-19 15:14:13 -07:00
Michael Davidsaver 0a20804294 fix tpool cleanup 2018-04-19 14:33:42 -07:00
Michael Davidsaver ff197f89cb testpvalink avoid false positive races 2018-04-19 14:13:07 -07:00
Michael Davidsaver c86472666a pvalink config # of workers 2018-04-18 15:14:27 -07:00
Michael Davidsaver e2147a95b2 minor 2018-04-18 14:50:10 -07:00
Michael Davidsaver d07d2dd30c pvalink put pvRequest 2018-04-18 14:49:43 -07:00
Michael Davidsaver 197e079ad5 pvalink time: key 2018-04-18 14:22:46 -07:00
Michael Davidsaver 27fd0868cf pvalink more processing 2018-04-18 12:29:39 -07:00
Michael Davidsaver e165919e84 pvalink: lset behavior 2018-04-18 12:03:53 -07:00
Michael Davidsaver 2934c8a1e4 minor 2018-04-18 10:46:52 -07:00
Michael Davidsaver c0ac33dc4b pvif handle NELM=1, NORD=0
Sometimes a DBF "scalar" isn't really a scalar.
2018-04-17 22:36:49 -07:00
Michael Davidsaver 4357d9079e preliminary docs for pva links 2018-04-17 20:39:55 -07:00
Michael Davidsaver afe5232653 pvalink pipeline option 2018-04-17 20:38:44 -07:00
Michael Davidsaver 1cc1d1857e fix DBR2PVD() DBF_ENUM handling
must treat DBF_ENUM differently in
pvif than in dbf_copy as the storage
types used are different.
2018-04-17 19:10:54 -07:00
Michael Davidsaver 6089591abb bump API and ABI versions for introduction of pv/qsrv.h and pva links
Only added new public symbols.
2018-04-17 19:10:54 -07:00
Michael Davidsaver c55d50c158 fix pvalink shutdown during tests 2018-04-17 19:10:54 -07:00
Michael Davidsaver 520ac5a0b5 introduce pv/qsrv.h
workaround EPICS+RTEMS make fault
2018-04-17 19:10:54 -07:00
Michael Davidsaver 9192a872c7 resurrect testpvalink 2018-04-17 19:10:54 -07:00
Michael Davidsaver 79bd35dd3c re-enable PVA links at runtime 2018-04-17 19:10:54 -07:00
Michael Davidsaver f0be2f7016 rewrite pvalink using 3.16 link support API 2018-04-17 19:10:54 -07:00
Michael Davidsaver d5ee917c6d run testgroupconfig
missing for some reason
2018-04-17 19:10:54 -07:00
Michael Davidsaver cd2c1762b7 pvd <-> dbf
Yet more type conversions.

This is a complement to pvif.h in that
PVIF calls into dbPut() which this code is
called by dbPut().
2018-04-17 19:10:54 -07:00
Michael Davidsaver 8b8e421a65 pvif type code mapping cleanup
DBR2PVD() remove default: so that missed case is a compile warning.
return -1 for invalid codes.

PVD2DBR() correct int64 (not used so far)
2018-04-17 19:10:54 -07:00
Michael Davidsaver f3173682fb add weak_ptr threaded work queue 2018-04-17 19:10:54 -07:00
78 changed files with 7676 additions and 2113 deletions
+103
View File
@@ -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
+14
View File
@@ -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
-20
View File
@@ -1,20 +0,0 @@
#!/bin/sh
set -e -x
# set RTEMS to eg. "4.9" or "4.10"
# requires qemu, bison, flex, texinfo, install-info
if [ -n "$RTEMS" ]
then
# find local qemu-system-i386
echo -n "Using QEMU: "
type qemu-system-i386 || echo "Missing qemu"
EXTRA=RTEMS_QEMU_FIXUPS=YES
fi
make -j2 $EXTRA
if [ "$TEST" != "NO" ]
then
make tapfiles
make -s test-results
fi
-153
View File
@@ -1,153 +0,0 @@
#!/bin/sh
set -e -x
CURDIR="$PWD"
cat << EOF > $CURDIR/configure/RELEASE.local
EPICS_BASE=$HOME/.source/epics-base
EOF
install -d "$HOME/.source"
cd "$HOME/.source"
add_base_module() {
MODULE=$1
BRANCH=$2
( cd epics-base/modules && \
git clone --quiet --depth 5 --branch $MODULE/$BRANCH https://github.com/${REPOBASE:-epics-base}/epics-base.git $MODULE && \
cd $MODULE && git log -n1 )
}
add_gh_module() {
MODULE=$1
REPOOWNER=$2
REPONAME=$3
BRANCH=$4
( cd epics-base/modules && \
git clone --quiet --depth 5 --branch $BRANCH https://github.com/$REPOOWNER/$REPONAME.git $MODULE && \
cd $MODULE && git log -n1 )
}
add_gh_flat() {
MODULE=$1
REPOOWNER=$2
REPONAME=$3
BRANCH=$4
MODULE_UC=$(echo $MODULE | tr 'a-z' 'A-Z')
( git clone --quiet --depth 5 --branch $BRANCH https://github.com/$REPOOWNER/$REPONAME.git $MODULE && \
cd $MODULE && git log -n1 )
cat < $CURDIR/configure/RELEASE.local > $MODULE/configure/RELEASE.local
cat << EOF >> $CURDIR/configure/RELEASE.local
${MODULE_UC}=$HOME/.source/$MODULE
EOF
}
if [ "$BRBASE" ]
then
git clone --quiet --depth 5 --branch "$BRBASE" https://github.com/${REPOBASE:-epics-base}/epics-base.git epics-base
(cd epics-base && git log -n1 )
add_gh_flat pvData ${REPOPVD:-epics-base} pvDataCPP ${BRPVD:-master}
add_gh_flat pvAccess ${REPOPVA:-epics-base} pvAccessCPP ${BRPVA:-master}
else
git clone --quiet --depth 5 --branch core/"${BRCORE:-master}" https://github.com/${REPOBASE:-epics-base}/epics-base.git epics-base
( cd epics-base && git log -n1 )
add_base_module libcom "${BRLIBCOM:-master}"
add_base_module ca "${BRCA:-master}"
add_base_module database "${BRDATABASE:-master}"
add_gh_module pvData ${REPOPVD:-epics-base} pvDataCPP ${BRPVD:-master}
add_gh_module pvAccess ${REPOPVA:-epics-base} pvAccessCPP ${BRPVA:-master}
fi
if [ -e $CURDIR/configure/RELEASE.local ]
then
cat $CURDIR/configure/RELEASE.local
fi
EPICS_HOST_ARCH=`sh epics-base/startup/EpicsHostArch`
# requires wine and g++-mingw-w64-i686
if [ "$WINE" = "32" ]
then
echo "Cross mingw32"
sed -i -e '/CMPLR_PREFIX/d' epics-base/configure/os/CONFIG_SITE.linux-x86.win32-x86-mingw
cat << EOF >> epics-base/configure/os/CONFIG_SITE.linux-x86.win32-x86-mingw
CMPLR_PREFIX=i686-w64-mingw32-
EOF
cat << EOF >> epics-base/configure/CONFIG_SITE
CROSS_COMPILER_TARGET_ARCHS+=win32-x86-mingw
EOF
fi
if [ "$STATIC" = "YES" ]
then
echo "Build static libraries/executables"
cat << EOF >> epics-base/configure/CONFIG_SITE
SHARED_LIBRARIES=NO
STATIC_BUILD=YES
EOF
fi
case "$CMPLR" in
clang)
echo "Host compiler is clang"
cat << EOF >> epics-base/configure/os/CONFIG_SITE.Common.$EPICS_HOST_ARCH
GNU = NO
CMPLR_CLASS = clang
CC = clang
CCC = clang++
EOF
# hack
sed -i -e 's/CMPLR_CLASS = gcc/CMPLR_CLASS = clang/' epics-base/configure/CONFIG.gnuCommon
clang --version
;;
*)
echo "Host compiler is default"
gcc --version
;;
esac
cat <<EOF >> epics-base/configure/CONFIG_SITE
USR_CPPFLAGS += $USR_CPPFLAGS
USR_CFLAGS += $USR_CFLAGS
USR_CXXFLAGS += $USR_CXXFLAGS
EOF
# set RTEMS to eg. "4.9" or "4.10"
# requires qemu, bison, flex, texinfo, install-info
if [ -n "$RTEMS" ]
then
echo "Cross RTEMS${RTEMS} for pc386"
install -d /home/travis/.cache
curl -L "https://github.com/mdavidsaver/rsb/releases/download/travis-20160306-2/rtems${RTEMS}-i386-trusty-20190306-2.tar.gz" \
| tar -C /home/travis/.cache -xj
sed -i -e '/^RTEMS_VERSION/d' -e '/^RTEMS_BASE/d' epics-base/configure/os/CONFIG_SITE.Common.RTEMS
cat << EOF >> epics-base/configure/os/CONFIG_SITE.Common.RTEMS
RTEMS_VERSION=$RTEMS
RTEMS_BASE=/home/travis/.cache/rtems${RTEMS}-i386
EOF
cat << EOF >> epics-base/configure/CONFIG_SITE
CROSS_COMPILER_TARGET_ARCHS+=RTEMS-pc386
EOF
# find local qemu-system-i386
export PATH="$HOME/.cache/qemu/usr/bin:$PATH"
echo -n "Using QEMU: "
type qemu-system-i386 || echo "Missing qemu"
cat <<EOF >> epics-base/configure/CONFIG_SITE
RTEMS_QEMU_FIXUPS=YES
EOF
fi
make -j2 -C epics-base $EXTRA
if [ "$BRBASE" ]
then
make -j2 -C pvData $EXTRA
make -j2 -C pvAccess $EXTRA
fi
find epics-base/include
find epics-base/lib
+179
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
[submodule ".ci"]
path = .ci
url = https://github.com/epics-base/ci-scripts
+17
View File
@@ -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
-29
View File
@@ -1,29 +0,0 @@
sudo: false
dist: trusty
language: c++
compiler:
- gcc
addons:
apt:
packages:
- libreadline6-dev
- libncurses5-dev
- perl
- clang
- g++-mingw-w64-i686
install:
- ./.ci/travis-prepare.sh
script:
- ./.ci/travis-build.sh
env:
- BRCORE=master BRLIBCOM=master BRPVD=master BRPVA=master
- CMPLR=clang
- EXTRA="CMD_CXXFLAGS=-std=c++98"
- EXTRA="CMD_CXXFLAGS=-std=c++11"
- CMPLR=clang EXTRA="CMD_CXXFLAGS=-std=c++11"
- WINE=32 TEST=NO STATIC=YES
- WINE=32 TEST=NO STATIC=NO
- RTEMS=4.10 TEST=NO
- RTEMS=4.9 TEST=NO
- BRBASE=3.16
- BRBASE=3.15
+2497
View File
File diff suppressed because it is too large Load Diff
+13 -12
View File
@@ -1,3 +1,5 @@
See main [documentation](https://epics-base.github.io/pva2pva/) page.
This repository contains two distinct pieces of software.
QSRV
@@ -19,28 +21,29 @@ 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
------------
- [epics-base](http://www.aps.anl.gov/epics/) >= 3.15.3
- and
- [pvDataCPP](http://epics-pvdata.sourceforge.net/)
- [pvAccessCPP](http://epics-pvdata.sourceforge.net/)
or bundled with
- [epics-base](http://www.aps.anl.gov/epics/) >= 7.0.1
Building
--------
To build all dependencies from source:
```sh
git clone https://github.com/epics-base/epics-base.git
git clone https://github.com/epics-base/pvDataCPP.git
git clone https://github.com/epics-base/pvAccessCPP.git
git clone https://github.com/mdavidsaver/pva2pva.git
git clone --recursive https://github.com/epics-base/epics-base.git
make -C epics-base
make -C pvDataCPP
make -C pvAccessCPP
make -C pva2pva
```
Running QSRV
@@ -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
```
-2
View File
@@ -16,8 +16,6 @@
#include "weakmap.h"
#include "weakset.h"
#include <shareLib.h>
struct TestPV;
struct TestPVChannel;
struct TestPVMonitor;
+9 -3
View File
@@ -1,9 +1,15 @@
# Module (source) version
EPICS_QSRV_MAJOR_VERSION = 1
EPICS_QSRV_MINOR_VERSION = 0
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 = 0
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
+3
View File
@@ -38,5 +38,8 @@ CHECK_RELEASE = YES
# we don't actually use 'rset' explicitly, but quiet some warnings
USR_CPPFLAGS += -DUSE_TYPED_RSET
# we use std::min()/max() and don't want conflicting MSVC defs
USR_CPPFLAGS_WIN32 += -DNOMINMAX
-include $(TOP)/../CONFIG_SITE.local
-include $(TOP)/configure/CONFIG_SITE.local
+1 -1
View File
@@ -38,7 +38,7 @@ PROJECT_NAME = pva2pva
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 1.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
+7
View File
@@ -0,0 +1,7 @@
.wy-side-nav-search {
background-color: #18334B;
}
.wy-side-nav-search input[type="text"] {
border-color: #18334b;
}
+78
View File
@@ -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

+16
View File
@@ -0,0 +1,16 @@
pva2pva (QSRV / pvAccess Gateway)
=================================
.. toctree::
:hidden:
EPICS Website <https://epics-controls.org>
EPICS Documentation Home <https://docs.epics-controls.org>
.. toctree::
:maxdepth: 1
:caption: pva2pva
Reference Manual and API Documentation <https://docs.epics-controls.org/projects/pva2pva/en/latest/doxygen>
Source Code Repository on GitHub <https://github.com/epics-base/pva2pva>
@@ -1,6 +1,7 @@
/**
@mainpage pva2pva Home of QSRV and pvAccess 2 pvAccess gateway
- [Source](https://github.com/epics-base/pva2pva)
- [Download](https://sourceforge.net/projects/epics-pvdata/files/)
@section qsrv QSRV
@@ -10,7 +11,9 @@ 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
+377
View File
@@ -0,0 +1,377 @@
/**
@page qsrv_page QSRV
@section qsrv_config QSRV Configuration
By default QSRV exposes all Process Variables (fields of process database records).
In addition to these "single" PVs are special "group" PVs.
@subsection qsrv_single Single PVs
"single" PVs are the same set of names server by the Channel Access server (RSRV).
This is all accessible record fields.
So all data which is accessible via Channel Access is also accessible via PVAccess.
QSRV presents all "single" PVs as Structures conforming to the
Normative Types NTScalar, NTScalarArray, or NTEnum depending on the native DBF field type.
@subsection qsrv_group_def Group PV definitions
A group is defined using a JSON syntax.
Groups are defined with respect to a Group Name,
which is also the PV name.
So unlike records, the "field" of a group have a different meaning.
Group field names are _not_ part of the PV name.
A group definition may be split among several records,
or included in separate JSON file(s).
For example of a group including two records is:
@code
record(ai, "rec:X") {
info(Q:group, {
"grp:name": {
"X": {+channel:"VAL"}
}
})
}
record(ai, "rec:Y") {
info(Q:group, {
"grp:name": {
"Y": {+channel:"VAL"}
}
})
}
@endcode
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
$ pvget grp:name
grp:name
structure
epics:nt/NTScalar:1.0 X
double value 0
alarm_t alarm INVALID DRIVER UDF
time_t timeStamp <undefined> 0
...
epics:nt/NTScalar:1.0 Y
double value 0
alarm_t alarm INVALID DRIVER UDF
time_t timeStamp <undefined> 0
...
@endcode
@subsection qsrv_group_ref Group PV reference
@code
record(...) {
info(Q:group, {
"<group_name>":{
+id:"some/NT:1.0", # top level ID
+meta:"FLD", # map top level alarm/timeStamp
+atomic:true, # whether monitors default to multi-locking atomicity
"<field.name>":{
+type:"scalar", # controls how map VAL mapped onto <field.name>
+channel:"VAL",
+id:"some/NT:1.0",
+trigger:"*", # "*" or comma seperated list of <field.name>s
+putorder:0, # set for fields where put is allowed, processing done in increasing order
}
}
})
}
@endcode
@subsubsection qsrv_group_map_types Field mapping types
@li "scalar" or ""
@li "plain"
@li "any"
@li "meta"
@li "proc"
The "scalar" mapping places an NTScalar or NTScalarArray as a sub-structure.
The "plain" mapping ignores all meta-data and places only the "value" as a field.
The "value" is equivalent to '.value' of the equivalent NTScalar/NTScalarArray as a field.
The "any" mapping places a variant union into which the "value" is placed.
The "meta" mapping ignores the "value" and places only the alarm and time
meta-data as sub-fields.
The special group level tag 'meta:""' allows these meta-data fields to be
placed in the top-level structure.
The "proc" mapping uses neither "value" nor meta-data.
Instead the target record is processed during a put.
@subsubsection qsrv_group_map_trig Field Update Triggers
The field triggers define how changes to the consitutent field
are translated into a subscription update to the group.
The most use of these are "" which means that changes to the field
are ignored, and do not result group update.
And "*" which results in a group update containing the most recent
values/meta-data of all fields.
It may be useful to specify a comma seperated list of field names
so that changes may partially update the group.
@subsection qsrv_stamp QSRV Timestamp Options
QSRV has the ability to perform certain transformations on the timestamp before transporting it.
The mechanism for configuring this is the "Q:time:tag" info() tag.
@subsubsection qsrv_stamp_nslsb Nano-seconds least significant bits
Setting "Q:time:tag" to a value of "nsec:lsb:#", where # is a number between 0 and 32,
will split the nanoseconds value stored in the associated record.
The least significant # bits are stored in the 'timeStamp.userTag' field.
While the remaining 32-# bits are stored in 'timeStamp.nanoseconds' (without shifting).
For example, in the following situation 20 bits are split off into userTag.
If the nanoseconds part of the record timestamp is 0x12345678,
then the PVD structure would include "timeStamp.nanoseconds=0x12300000"
and "timeStamp.userTag=0x45678".
@code
record(ai, "...") {
info(Q:time:tag, "nsec:lsb:20")
}
@endcode
@subsection qsrv_form QSRV Display Form Option
The value of the OPI display form hint ('display.form') can be set set with the "Q:form" info() tag.
This hint, along with 'display.precision', is used by some OPI clients to control how values are rendered.
The text value of the tag must be one of the following choices.
@li Default
@li String
@li Binary
@li Decimal
@li Hex
@li Exponential
@li Engineering
@code
record(ai, "...") {
info(Q:form, "Default") # implied default
}
@endcode
@subsection qsrv_aslib Access Security
QSRV will enforce an optional access control policy file (.acf) loaded by the usual means (cf. asSetFilename() ).
This policy is applied to both Single and Group PVs. With Group PVs, restrictions are not defined for the group,
but rather for the individual member records. The same policy will be applied regardess of how a record
is accessed (individually, or through a group).
Policy application differs from CA (RSRV) in several ways:
* Client hostname is always the numeric IP address. HAG() entries must either contained numeric IP addresses,
or that asCheckClientIP=1 flag must be set to translate hostnames into IPs on ACF file load (effects CA server as well).
This prevents clients from trivially forging "hostname".
* In additional to client usernames. UAG definitions may contained items beginning with "role/" which are matched
against the list of groups of which the client username is a member. Username to group lookup is done internally
to QSRV, and depends on IOC host authentication configuration. Note that this is still based on the client provided
username string.
@code
UAG(special) {
someone, "role/op"
}
@endcode
The "special" UAG will match CA or PVA clients with the username "someone".
It will also match a PVA client if the client provided username is a member
of the "op" group (supported on POSIX targets and Windows).
@subsection qsrv_link PVAccess Links
When built against Base >= 3.16.1, support is enabled for PVAccess links,
which are analogous to Channel Access (CA) links. However, the syntax
for PVA links is quite different.
@note The "dbjlr" and "dbpvar" IOC shell command provide information about PVA links in a running IOC.
A simple configuration using defaults is
@code
record(longin, "tgt") {}
record(longin, "src") {
field(INP, {pva:"tgt"})
}
@endcode
This is a shorthand for
@code
record(longin, "tgt") {}
record(longin, "src") {
field(INP, {pva:{pv:"tgt"}})
}
@endcode
Some additional keys (beyond "pv") may be used.
Defaults are shown below:
@code
record(longin, "tgt") {}
record(longin, "src") {
field(INP, {pva:{
pv:"tgt",
field:"", # may be a sub-field
local:false,# Require local PV
Q:4, # monitor queue depth
pipeline:false, # require that server uses monitor flow control protocol
proc:none, # Request record processing (side-effects).
sevr:false, # Maximize severity.
time:false, # set record time during getValue
monorder:0, # Order of record processing as a result of CP and CPP
retry:false,# allow Put while disconnected.
always:false,# CP/CPP input link process even when .value field hasn't changed
defer:false # Defer put
}})
}
@endcode
@subsubsection qsrv_link_pv pv: Target PV name
The PV name to search for.
This is the same name which could be used with 'pvget' or other client tools.
@subsubsection qsrv_link_field field: Structure field name
The name of a sub-field of the remotely provided Structure.
By default, an empty string "" uses the top-level Structure.
If the top level structure, or a sub-structure is selected, then
it is expeccted to conform to NTScalar, NTScalarArray, or NTEnum
to extract value and meta-data.
If the sub-field is an PVScalar or PVScalarArray, then a value
will be taken from it, but not meta-data will be available.
@todo Ability to traverse through unions and into structure arrays (as with group mappings).
@subsubsection qsrv_link_local local: Require local PV
When true, link will not connect unless the named PV is provided by the local (QSRV) data provider.
@subsubsection qsrv_link_Q Q: Monitor queue depth
Requests a certain monitor queue depth.
The server may, or may not, take this into consideration when selecting
a queue depth.
@subsubsection qsrv_link_pipeline pipeline: Monitor flow control
Expect that the server supports PVA monitor flow control.
If not, then the subscription will stall (ick.)
@subsubsection qsrv_link_proc proc: Request record processing (side-effects)
The meaning of this option depends on the direction of the link.
For output links, this option allows a request for remote processing (side-effects).
@li none (default) - Make no special request. Uses a server specific default.
@li false, "NPP" - Request to skip processing.
@li true, "PP" - Request to force processing.
@li "CP", "CPP" - For output links, an alias for "PP".
For input links, this option controls whether the record containing
the PVA link will be processed when subscription events are received.
@li none (default), false, "NPP" - Do not process on subscription updates.
@li true, "CP" - Always process on subscription updates.
@li "PP", "CPP" - Process on subscription updates if SCAN=Passive
@subsubsection qsrv_link_sevr sevr: Alarm propagation
This option controls whether reading a value from an input PVA link
has the addition effect of propagating any alarm via the Maximize
Severity process.
@li false - Do not maximize severity.
@li true - Maximize alarm severity
@li "MSI" - Maximize only if the remote severity is INVALID.
@subsubsection qsrv_link_time time: Time propagation
Somewhat analogous to sevr: applied to timestamp.
When true, the record TIME field is updated when the link value is read.
@warning TSEL must be set to -2 for time:true to have an effect.
@subsubsection qsrv_link_monorder monorder: Monitor processing order
When multiple record target the same target PV, and request processing
on subscription updates. This option allows the order of processing
to be specified.
Record are processed in increasing order.
monorder=-1 is processed before monorder=0.
Both are processed before monorder=1.
@subsubsection qsrv_link_defer defer: Defer put
By default (defer=false) an output link will immediately
start a PVA Put operation. defer=true will store the
new value in an internal cache, but not start a PVA Put.
This option, in combination with field: allows a single
Put to contain updates to multiple sub-fields.
@subsubsection qsrv_link_retry retry: Put while disconnected
Allow a Put operation to be queued while the link is disconnected.
The Put will be executed when the link becomes connected.
@subsubsection qsrv_link_always always: CP/CPP always process
By default (always:false) a subscription update will only cause a CP input link
to scan if the structure field (cf. field: option) is marked as changed.
Set to true to override this, and always process the link.
@subsubsection qsrv_link_sem Link semantics/behavior
This section attempts to answer some questions about how links behave in certain situations.
Links are evaluated in three basic contexts.
@li dbPutLink()/dbScanFwdLink()
@li dbGetLink() of non-CP link
@li dbGetLink() during a scan resulting from a CP link.
An input link can bring in a Value as well as meta-data, alarm, time, and display/control info.
For input links, the PVA link engine attempts to always maintain consistency between Value, alarm, and time.
However, consistency between these, and the display/control info is only ensured during a CP scan.
*/
-144
View File
@@ -1,144 +0,0 @@
/**
@page qsrv_page QSRV
@section qsrv_config QSRV Configuration
By default QSRV exposes all Process Variables (fields of process database records).
In addition to these "single" PVs are special "group" PVs.
@subsection qsrv_single Single PVs
"single" PVs are the same set of names server by the Channel Access server (RSRV).
This is all accessible record fields.
So all data which is accessible via Channel Access is also accessible via PVAccess.
QSRV presents all "single" PVs as Structures conforming to the
Normative Types NTScalar, NTScalarArray, or NTEnum depending on the native DBF field type.
@subsection qsrv_group_def Group PV definitions
A group is defined using a JSON syntax.
Groups are defined with respect to a Group Name,
which is also the PV name.
So unlike records, the "field" of a group have a different meaning.
Group field names are _not_ part of the PV name.
A group definition is split among several records.
For example of a group including two records is:
@code
record(ai, "rec:X") {
info(Q:group, {
"grp:name": {
"X": {+channel:"VAL"}
}
})
}
record(ai, "rec:Y") {
info(Q:group, {
"grp:name": {
"Y": {+channel:"VAL"}
}
})
}
@endcode
This group, named "grp:name", has two fields "X" and "Y".
@code
$ pvget grp:name
grp:name
structure
epics:nt/NTScalar:1.0 X
double value 0
alarm_t alarm INVALID DRIVER UDF
time_t timeStamp <undefined> 0
...
epics:nt/NTScalar:1.0 Y
double value 0
alarm_t alarm INVALID DRIVER UDF
time_t timeStamp <undefined> 0
...
@endcode
@subsection qsrv_group_ref Group PV reference
@code
record(...) {
info(Q:group, {
"<group_name>":{
+id:"some/NT:1.0", # top level ID
+meta:"FLD", # map top level alarm/timeStamp
+atomic:true, # whether monitors default to multi-locking atomicity
"<field.name>":{
+type:"scalar", # controls how map VAL mapped onto <field.name>
+channel:"VAL",
+id:"some/NT:1.0",
+trigger:"*", # "*" or comma seperated list of <field.name>s
+putorder:0, # set for fields where put is allowed, processing done in increasing order
}
}
})
}
@endcode
@subsubsection qsrv_group_map_types Field mapping types
@li "scalar" or ""
@li "plain"
@li "any"
@li "meta"
@li "proc"
The "scalar" mapping places an NTScalar or NTScalarArray as a sub-structure.
The "plain" mapping ignores all meta-data and places only the "value" as a field.
The "value" is equivalent to '.value' of the equivalent NTScalar/NTScalarArray as a field.
The "any" mapping places a variant union into which the "value" is placed.
The "meta" mapping ignores the "value" and places only the alarm and time
meta-data as sub-fields.
The special group level tag 'meta:""' allows these meta-data fields to be
placed in the top-level structure.
The "proc" mapping uses neither "value" nor meta-data.
Instead the target record is processed during a put.
@subsubsection qsrv_group_map_trig Field Update Triggers
The field triggers define how changes to the consitutent field
are translated into a subscription update to the group.
The most use of these are "" which means that changes to the field
are ignored, and do not result group update.
And "*" which results in a group update containing the most recent
values/meta-data of all fields.
It may be useful to specify a comma seperated list of field names
so that changes may partially update the group.
@subsection qsrv_stamp QSRV Timestamp Options
QSRV has the ability to perform certain transformations on the timestamp before transporting it.
The mechanism for configuring this is the "Q:time:tag" info() tag.
@subsubsection qsrv_stamp_nslsb Nano-seconds least significant bits
Setting "Q:time:tag" to a value of "nsec:lsb:#", where # is a number between 0 and 32,
will split the nanoseconds value stored in the associated record.
The least significant # bits are stored in the 'timeStamp.userTag' field.
While the remaining 32-# bits are stored in 'timeStamp.nanoseconds' (without shifting).
For example, in the following situation 16 bits are split off.
If the nanoseconds part of the record timestamp is 0x12345678,
then the PVD structure would include "timeStamp.nanoseconds=0x12300000"
and "timeStamp.userTag=0x45678".
@code
record(ai, "...") {
info(Q:time:tag, "nsec:lsb:20")
}
@endcode
*/
+81
View File
@@ -0,0 +1,81 @@
/**
@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
- Removals
- Drop the broken ioccircle and ioccircle2 examples.
- Fixes
- Fix QSRV monitor locking causing crash
- Fix Windows DLL import/export errors
- Correctly handle empty "scalar" case of NELM=1, NORD=0.
- Additions
- QSRV implement channelList() (aka. 'pvlist') with list of record and group names.
- @ref qsrv_link type (requires Base >= 3.16.1)
Release 1.0.0 (Dec 2017)
========================
Initial Release
*/
-23
View File
@@ -1,23 +0,0 @@
/**
@page release_notes Release Notes
Release 1.1.0 (UNRELEASED)
==========================
- Incompatible changes
- Requires pvDataCPP >= 7.1.0-pre1
- Requires pvAccessCPP >= 6.1.0-pre1
- Removals
- Drop the broken ioccircle and ioccircle2 examples.
- Fixes
- Fix QSRV monitor locking causing crash
- Fix Windows DLL import/export errors
- Correctly handle empty "scalar" case of NELM=1, NORD=0.
- Additions
- QSRV implement channelList() (aka. 'pvlist') with list of record and group names.
Release 1.0.0 (Dec 2017)
========================
Initial Release
-26
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
TOP = ../..
include $(TOP)/configure/CONFIG
ARCH = linux-x86_64-debug
TARGETS = envPaths
include $(TOP)/configure/RULES.ioc
+5
View File
@@ -0,0 +1,5 @@
IOC for testing PVA links
Primary test is st.cmd. st2.cmd is run/stopped concurrently
to test remote connect/disconnect. st2a.cmd to test type
change.
+106
View File
@@ -0,0 +1,106 @@
record(ai, "$(P)target") {}
# local input link shorthand
record(longin, "$(P)inp1") {
field(INP , {pva:"$(P)target"})
alias("$(P)inp1:alias")
}
# local input link longhand
record(longin, "$(P)inp2") {
field(INP , {pva:{pv:"$(P)target"}})
}
# remote input link
record(longin, "$(P)inp3") {
field(INP , {pva:"invalid:pv:name"})
}
# null like (requires local, but not local)
record(longin, "$(P)inp4") {
field(INP , {pva:{pv:"invalid:pv:name", local:true}})
}
# local input link w/ CP (process on update)
record(calc, "$(P)cnt") {
field(SCAN, "1 second")
field(INPA, "$(P)cnt")
field(INPB, "9")
field(CALC, "A<B?A+1:0")
field(HIGH, "7")
field(HSV , "MAJOR")
}
record(longin, "$(P)track") {
field(INP , {pva:{
pv:"$(P)cnt",
proc:"CP",
sevr:true,
time:true
}})
field(TSE, "-2")
}
# remote input link w/ CP (process on update)
record(longin, "$(P)track2") {
field(INP , {pva:{
pv:"$(P)cnt:rmt",
proc:"CP",
sevr:true,
time:true
}})
field(TSE, "-2")
}
# local output link
record(longout, "$(P)out1") {
field(OUT , {pva:{pv:"$(P)out:tgt"}})
field(TPRO, "1")
}
record(longout, "$(P)out:tgt") {
field(TPRO, "1")
}
# remote output link
record(longout, "$(P)out2") {
field(OUT , {pva:{pv:"$(P)out:rmt"}})
field(TPRO, "1")
}
# remote output link w/ retry (queue put while disconnected)
record(longout, "$(P)out3") {
field(OUT , {pva:{pv:"$(P)out:rmt", retry:true}})
field(TPRO, "1")
#info(base:lsetDebug, "YES")
}
# output link to waveform
record(aao, "$(P)out4:wf") {
field(FTVL, "DOUBLE")
field(NELM, "10")
field(OUT , {pva:{pv:"$(P)target:wf"}})
}
# input link to waveform
record(aao, "$(P)target:wf") {
field(FTVL, "DOUBLE")
field(NELM, "10")
}
record(aai, "$(P)inp5:wf") {
field(FTVL, "DOUBLE")
field(NELM, "10")
field(INP , {pva:{pv:"$(P)target:wf"}})
}
# test forward link
record(calc, "$(P)fcnt1") {
field(CALC, "VAL+1")
field(FLNK, {pva:{pv:"$(P)fcnt2"}})
info(base:lsetDebug, "YES")
}
record(calc, "$(P)fcnt2") {
field(CALC, "VAL+1")
}
+14
View File
@@ -0,0 +1,14 @@
# use with pvalink.db, run in a second IOC
record(calc, "$(P)cnt:rmt") {
field(SCAN, "1 second")
field(INPA, "$(P)cnt")
field(INPB, "9")
field(CALC, "A<B?A+0.1:0")
field(HIGH, "7")
field(HSV , "MAJOR")
}
record(longout, "$(P)out:rmt") {
field(TPRO, "1")
}
+18
View File
@@ -0,0 +1,18 @@
# use with pvalink.db, run in a second IOC
# use instead of pvalink2.db to cause a type change
record(calcout, "$(P)cnt:rmtx") {
field(SCAN, "1 second")
field(OUT , "$(P)cnt:rmt PP")
field(INPA, "$(P)cnt")
field(INPB, "9")
field(CALC, "A<B?A+1:0")
field(HIGH, "7")
field(HSV , "MAJOR")
}
record(longin, "$(P)cnt:rmt") {}
record(stringout, "$(P)out:rmt") {
field(TPRO, "1")
}
+5
View File
@@ -0,0 +1,5 @@
#!../../bin/linux-x86_64-debug/softIocPVA
dbLoadRecords("pvalink.db","P=TST:")
iocInit()
+5
View File
@@ -0,0 +1,5 @@
#!../../bin/linux-x86_64-debug/softIocPVA
dbLoadRecords("pvalink2.db","P=TST:")
iocInit()
+5
View File
@@ -0,0 +1,5 @@
#!../../bin/linux-x86_64-debug/softIocPVA
dbLoadRecords("pvalink2a.db","P=TST:")
iocInit()
+3
View File
@@ -14,6 +14,7 @@
*/
{
"version":1,
"readOnly":false,
"clients":[
{
"name":"theclient",
@@ -29,6 +30,8 @@
"name":"theserver",
"clients":["theclient"],
"interface":"127.0.0.1",
"addrlist":"127.255.255.255",
"autoaddrlist":false,
"serverport":5075,
"bcastport":5076
}
-2
View File
@@ -14,8 +14,6 @@
#include "weakmap.h"
#include "weakset.h"
#include <shareLib.h>
struct ChannelCache;
struct ChannelCacheEntry;
struct MonitorUser;
-2
View File
@@ -3,8 +3,6 @@
#include <pv/pvAccess.h>
#include <shareLib.h>
#include "chancache.h"
struct GWChannel : public epics::pvAccess::Channel
+56 -4
View File
@@ -13,6 +13,7 @@
#include <epicsGetopt.h>
#include <iocsh.h>
#include <epicsTimer.h>
#include <libComRegister.h>
#include <pv/json.h>
@@ -23,16 +24,21 @@
#include <pv/reftrack.h>
#include <pv/iocreftrack.h>
#include <pv/iocshelper.h>
#include <pv/logger.h>
#include "server.h"
#include "pva2pva.h"
namespace pvd = epics::pvData;
namespace pva = epics::pvAccess;
extern int p2pReadOnly;
namespace {
pvd::StructureConstPtr schema(pvd::getFieldCreate()->createFieldBuilder()
->add("version", pvd::pvUInt)
->add("readOnly", pvd::pvBoolean)
->addNestedStructureArray("clients")
->add("name", pvd::pvString)
->add("provider", pvd::pvString)
@@ -45,6 +51,8 @@ pvd::StructureConstPtr schema(pvd::getFieldCreate()->createFieldBuilder()
->add("name", pvd::pvString)
->addArray("clients", pvd::pvString)
->add("interface", pvd::pvString)
->add("addrlist", pvd::pvString)
->add("autoaddrlist", pvd::pvBoolean)
->add("serverport", pvd::pvUShort)
->add("bcastport", pvd::pvUShort)
->add("control_prefix", pvd::pvString)
@@ -62,9 +70,12 @@ void getargs(ServerConfig& arg, int argc, char *argv[])
int opt;
bool checkonly = false;
while( (opt=getopt(argc, argv, "vhiIC"))!=-1)
while( (opt=getopt(argc, argv, "qvhiIC"))!=-1)
{
switch(opt) {
case 'q':
arg.debug--;
break;
case 'v':
arg.debug++;
break;
@@ -95,6 +106,8 @@ void getargs(ServerConfig& arg, int argc, char *argv[])
std::ifstream strm(argv[optind]);
pvd::parseJSON(strm, arg.conf);
p2pReadOnly = arg.conf->getSubFieldT<pvd::PVScalar>("readOnly")->getAs<pvd::boolean>();
unsigned version = arg.conf->getSubFieldT<pvd::PVUInt>("version")->get();
if(version==0) {
std::cerr<<"Warning: config file missing \"version\" key. Assuming 1\n";
@@ -117,15 +130,19 @@ void getargs(ServerConfig& arg, int argc, char *argv[])
}
}
GWServerChannelProvider::shared_pointer configure_client(const pvd::PVStructurePtr& conf)
GWServerChannelProvider::shared_pointer configure_client(ServerConfig& arg, const pvd::PVStructurePtr& conf)
{
std::string name(conf->getSubFieldT<pvd::PVString>("name")->get());
std::string provider(conf->getSubFieldT<pvd::PVString>("provider")->get());
LOG(pva::logLevelInfo, "Configure client '%s' with provider '%s'", name.c_str(), provider.c_str());
pva::Configuration::shared_pointer C(pva::ConfigurationBuilder()
.add("EPICS_PVA_ADDR_LIST", conf->getSubFieldT<pvd::PVString>("addrlist")->get())
.add("EPICS_PVA_AUTO_ADDR_LIST", conf->getSubFieldT<pvd::PVBoolean>("autoaddrlist")->get())
.add("EPICS_PVA_AUTO_ADDR_LIST", conf->getSubFieldT<pvd::PVScalar>("autoaddrlist")->getAs<std::string>())
.add("EPICS_PVA_SERVER_PORT", conf->getSubFieldT<pvd::PVScalar>("serverport")->getAs<pvd::uint16>())
.add("EPICS_PVA_BROADCAST_PORT", conf->getSubFieldT<pvd::PVScalar>("bcastport")->getAs<pvd::uint16>())
.add("EPICS_PVA_DEBUG", arg.debug>=5 ? 5 : 0)
.push_map()
.build());
@@ -139,10 +156,17 @@ GWServerChannelProvider::shared_pointer configure_client(const pvd::PVStructureP
pva::ServerContext::shared_pointer configure_server(ServerConfig& arg, const pvd::PVStructurePtr& conf)
{
std::string name(conf->getSubFieldT<pvd::PVString>("name")->get());
LOG(pva::logLevelInfo, "Configure server '%s'", name.c_str());
pva::Configuration::shared_pointer C(pva::ConfigurationBuilder()
.add("EPICS_PVAS_INTF_ADDR_LIST", conf->getSubFieldT<pvd::PVString>("interface")->get())
.add("EPICS_PVAS_BEACON_ADDR_LIST", conf->getSubFieldT<pvd::PVString>("addrlist")->get())
.add("EPICS_PVAS_AUTO_BEACON_ADDR_LIST", conf->getSubFieldT<pvd::PVScalar>("autoaddrlist")->getAs<std::string>())
.add("EPICS_PVAS_SERVER_PORT", conf->getSubFieldT<pvd::PVScalar>("serverport")->getAs<pvd::uint16>())
.add("EPICS_PVAS_BROADCAST_PORT", conf->getSubFieldT<pvd::PVScalar>("bcastport")->getAs<pvd::uint16>())
.add("EPICS_PVA_DEBUG", arg.debug>=5 ? 5 : 0)
.push_map()
.build());
@@ -222,6 +246,8 @@ int main(int argc, char *argv[])
epics::iocshRegister<int, const char*, &gwsr>("gwsr", "level", "channel");
epics::iocshRegister<int, const char*, const char*, &gwcr>("gwcr", "level", "client", "channel");
libComRegister();
registerReadOnly();
epics::registerRefCounter("ChannelCacheEntry", &ChannelCacheEntry::num_instances);
epics::registerRefCounter("ChannelCacheEntry::CRequester", &ChannelCacheEntry::CRequester::num_instances);
epics::registerRefCounter("GWChannel", &GWChannel::num_instances);
@@ -232,6 +258,32 @@ int main(int argc, char *argv[])
theserver = &arg;
getargs(arg, argc, argv);
if(arg.debug>0)
std::cout<<"Notice: This p2p gateway prototype has been superceded by the p4p.gw gateway\n"
" which has exciting new features including granular access control,\n"
" and status PVs including bandwidth usage reports.\n"
" p2p is considered deprecated by its author, and will receive\n"
" minimal maintainance effort going forward.\n"
" Users are encouraged to migrate to p4p.gw.\n"
"\n"
" https://mdavidsaver.github.io/p4p/gw.html\n"
"\n";
pva::pvAccessLogLevel lvl;
if(arg.debug<0)
lvl = pva::logLevelError;
else if(arg.debug==0)
lvl = pva::logLevelWarn;
else if(arg.debug==1)
lvl = pva::logLevelInfo;
else if(arg.debug==2)
lvl = pva::logLevelDebug;
else if(arg.debug==3)
lvl = pva::logLevelTrace;
else if(arg.debug>=4)
lvl = pva::logLevelAll;
SET_LOG_LEVEL(lvl);
pva::ClientFactory::start();
pvd::PVStructureArray::const_svector arr;
@@ -250,7 +302,7 @@ int main(int argc, char *argv[])
if(it!=arg.clients.end())
throw std::runtime_error(std::string("Duplicate client name not allowed : ")+name);
arg.clients[name] = configure_client(client);
arg.clients[name] = configure_client(arg, client);
}
arr = arg.conf->getSubFieldT<pvd::PVStructureArray>("servers")->view();
+4 -6
View File
@@ -5,14 +5,12 @@
#include <pv/pvAccess.h>
#include <shareLib.h>
typedef epicsGuard<epicsMutex> Guard;
typedef epicsGuardRelease<epicsMutex> UnGuard;
epicsShareExtern void registerGWClientIocsh();
epicsShareExtern void gwServerShutdown();
epicsShareExtern void gwClientShutdown();
epicsShareExtern void registerReadOnly();
void registerGWClientIocsh();
void gwServerShutdown();
void gwClientShutdown();
void registerReadOnly();
#endif // PVA2PVA_H
+4 -15
View File
@@ -4,6 +4,7 @@
#include <epicsString.h>
#include <epicsTimer.h>
#include <pv/logger.h>
#include <pv/pvIntrospect.h> /* for pvdVersion.h */
#include <pv/epicsException.h>
#include <pv/serverContext.h>
@@ -40,14 +41,8 @@ GWServerChannelProvider::channelFind(std::string const & channelName,
if(!channelName.empty())
{
std::string newName;
// rewrite name
newName = channelName;
//newName[0] = 'y';
ChannelCacheEntry::shared_pointer ent(cache.lookup(newName));
LOG(pva::logLevelDebug, "Searching for '%s'", channelName.c_str());
ChannelCacheEntry::shared_pointer ent(cache.lookup(channelName));
if(ent) {
found = true;
ret = shared_from_this();
@@ -69,19 +64,13 @@ GWServerChannelProvider::createChannel(std::string const & channelName,
short priority, std::string const & addressx)
{
GWChannel::shared_pointer ret;
std::string newName;
std::string address = channelRequester->getRequesterName();
if(!channelName.empty())
{
// rewrite name
newName = channelName;
//newName[0] = 'y';
Guard G(cache.cacheLock);
ChannelCacheEntry::shared_pointer ent(cache.lookup(newName)); // recursively locks cacheLock
ChannelCacheEntry::shared_pointer ent(cache.lookup(channelName)); // recursively locks cacheLock
if(ent)
{
+1 -3
View File
@@ -6,8 +6,6 @@
#include "chancache.h"
#include "channel.h"
#include <shareLib.h>
struct GWServerChannelProvider :
public epics::pvAccess::ChannelProvider,
public epics::pvAccess::ChannelFind,
@@ -48,7 +46,7 @@ struct ServerConfig {
typedef std::map<std::string, epics::pvAccess::ServerContext::shared_pointer> servers_t;
servers_t servers;
ServerConfig() :debug(0), interactive(true) {}
ServerConfig() :debug(1), interactive(true) {}
void drop(const char *client, const char *channel);
void status_server(int lvl, const char *server);
+25 -25
View File
@@ -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
View File
@@ -94,7 +94,7 @@ private:
mutex_type mutex;
store_t store;
};
std::tr1::shared_ptr<data> m_data;
std::tr1::shared_ptr<data> _data;
//! Destroyer for a chained shared_ptr
//! which holds the unique() real strong
@@ -131,7 +131,7 @@ private:
};
public:
//! Construct a new empty set
weak_set() :m_data(new data) {}
weak_set() :_data(new data) {}
private:
//! Not copyable
@@ -143,22 +143,22 @@ public:
//! exchange the two sets.
//! @warning Not thread safe (exchanges mutexes as well)
void swap(weak_set& O) {
m_data.swap(O.m_data);
_data.swap(O._data);
}
//! Remove all (weak) entries from the set
//! @note Thread safe
void clear() {
guard_type G(m_data->mutex);
return m_data->store.clear();
guard_type G(_data->mutex);
return _data->store.clear();
}
//! Test if set is empty
//! @note Thread safe
//! @warning see size()
bool empty() const {
guard_type G(m_data->mutex);
return m_data->store.empty();
guard_type G(_data->mutex);
return _data->store.empty();
}
//! number of entries in the set at this moment
@@ -166,8 +166,8 @@ public:
//! @warning May be momentarily inaccurate (larger) due to dead refs.
//! which have not yet been removed.
size_t size() const {
guard_type G(m_data->mutex);
return m_data->store.size();
guard_type G(_data->mutex);
return _data->store.size();
}
//! Insert a new entry into the set
@@ -178,8 +178,8 @@ public:
//! Remove any (weak) ref to this object from the set
//! @returns the number of objects removed (0 or 1)
size_t erase(value_pointer& v) {
guard_type G(m_data->mutex);
return m_data->store.erase(v);
guard_type G(_data->mutex);
return _data->store.erase(v);
}
//! Return a set of strong references to all entries
@@ -197,7 +197,7 @@ public:
//! for use with batch operations.
//! @warning Use caution when swap()ing while holding this lock!
inline epicsMutex& mutex() const {
return m_data->mutex;
return _data->mutex;
}
//! an iterator-ish object which also locks the set during iteration
@@ -205,7 +205,7 @@ public:
weak_set& set;
epicsGuard<epicsMutex> guard;
typename store_t::iterator it, end;
XIterator(weak_set& S) :set(S), guard(S.mutex()), it(S.m_data->store.begin()), end(S.m_data->store.end()) {}
XIterator(weak_set& S) :set(S), guard(S.mutex()), it(S._data->store.begin()), end(S._data->store.end()) {}
//! yield the next live entry
value_pointer next() {
value_pointer ret;
@@ -229,14 +229,14 @@ void weak_set<T>::insert(value_pointer &v)
if(!v.unique())
throw std::invalid_argument("Only unique() references may be inserted");
guard_type G(m_data->mutex);
typename store_t::const_iterator it = m_data->store.find(v);
if(it==m_data->store.end()) { // new object
guard_type G(_data->mutex);
typename store_t::const_iterator it = _data->store.find(v);
if(it==_data->store.end()) { // new object
// wrapped strong ref. which removes from our map
value_pointer chainptr(v.get(), dtor(m_data, v));
value_pointer chainptr(v.get(), dtor(_data, v));
m_data->store.insert(chainptr);
_data->store.insert(chainptr);
v.swap(chainptr); // we only keep the chained pointer
} else {
@@ -253,9 +253,9 @@ typename weak_set<T>::set_type
weak_set<T>::lock_set() const
{
set_type ret;
guard_type G(m_data->mutex);
for(typename store_t::const_iterator it=m_data->store.begin(),
end=m_data->store.end(); it!=end; ++it)
guard_type G(_data->mutex);
for(typename store_t::const_iterator it=_data->store.begin(),
end=_data->store.end(); it!=end; ++it)
{
value_pointer P(it->lock());
if(P) ret.insert(P);
@@ -275,10 +275,10 @@ weak_set<T>::lock_vector() const
template<typename T>
void weak_set<T>::lock_vector(vector_type& ret) const
{
guard_type G(m_data->mutex);
ret.reserve(m_data->store.size());
for(typename store_t::const_iterator it=m_data->store.begin(),
end=m_data->store.end(); it!=end; ++it)
guard_type G(_data->mutex);
ret.reserve(_data->store.size());
for(typename store_t::const_iterator it=_data->store.begin(),
end=_data->store.end(); it!=end; ++it)
{
value_pointer P(it->lock());
if(P) ret.push_back(P);
+61 -2
View File
@@ -5,23 +5,54 @@ include $(TOP)/configure/CONFIG
# ADD MACRO DEFINITIONS AFTER THIS LINE
#=============================
ifeq ($(OS_CLASS),RTEMS)
# set by Base in configure/os/CONFIG.UnixCommon.Common
# then clobbered by included RTEMS make/host.cfg
# to drop the '-p'
MKDIR = mkdir -p
endif
LIBRARY += qsrv
SHRLIB_VERSION ?= $(EPICS_QSRV_ABI_MAJOR_VERSION).$(EPICS_QSRV_ABI_MINOR_VERSION)
USR_CPPFLAGS += -DQSRV_API_BUILDING
USR_CPPFLAGS += -I$(TOP)/common -I$(TOP)/p2pApp
INC += pv/qsrv.h
INC += pv/qsrvVersionNum.h
EXPANDVARS += EPICS_QSRV_MAJOR_VERSION
EXPANDVARS += EPICS_QSRV_MINOR_VERSION
EXPANDVARS += EPICS_QSRV_MAINTENANCE_VERSION
EXPANDVARS += EPICS_QSRV_DEVELOPMENT_FLAG
EXPANDVARS += EPICS_QSRV_ABI_MAJOR_VERSION
EXPANDVARS += EPICS_QSRV_ABI_MINOR_VERSION
EXPANDFLAGS += $(foreach var,$(EXPANDVARS),-D$(var)="$(strip $($(var)))")
qsrv_SRCS += pvif.cpp
qsrv_SRCS += qsrv.cpp
qsrv_SRCS += pdb.cpp
qsrv_SRCS += pdbsingle.cpp
#qsrv_SRCS += pvalink.cpp
qsrv_SRCS += demo.cpp
qsrv_SRCS += imagedemo.c
ifdef BASE_3_16
qsrv_SRCS += pdbgroup.cpp
qsrv_SRCS += configparse.cpp
qsrv_SRCS += dbf_copy.cpp
qsrv_SRCS += tpool.cpp
qsrv_SRCS += pvalink.cpp
qsrv_SRCS += pvalink_lset.cpp
qsrv_SRCS += pvalink_jlif.cpp
qsrv_SRCS += pvalink_link.cpp
qsrv_SRCS += pvalink_channel.cpp
else
qsrv_SRCS += pvalink_null.cpp
endif
qsrv_LIBS += pvAccess pvData
@@ -29,7 +60,9 @@ qsrv_LIBS += $(EPICS_BASE_IOC_LIBS)
FINAL_LOCATION ?= $(shell $(PERL) $(TOOLS)/fullPathName.pl $(INSTALL_LOCATION))
USR_CPPFLAGS += -DFINAL_LOCATION="\"$(FINAL_LOCATION)\""
# since we copy softMain.cpp from Base, use the same
# EPICS_BASE macro for our location
USR_CPPFLAGS += -DEPICS_BASE="\"$(FINAL_LOCATION)\""
PROD_IOC += softIocPVA
@@ -43,6 +76,11 @@ softIocPVA_LIBS += $(EPICS_BASE_IOC_LIBS)
DBD += softIocPVA.dbd
DBD += qsrv.dbd
ifneq ($(FINAL_LOCATION),$(EPICS_BASE))
# Copy from Base if we won't be installed there
DB_INSTALLS += $(EPICS_BASE)/db/softIocExit.db
endif
softIocPVA_DBD += softIoc.dbd
softIocPVA_DBD += PVAServerRegister.dbd
softIocPVA_DBD += qsrv.dbd
@@ -53,3 +91,24 @@ include $(TOP)/configure/RULES
#----------------------------------------
# ADD RULES AFTER THIS LINE
# Can't use EXPAND as generated headers must appear
# in O.Common, but EXPAND emits rules for O.$(T_A)
../O.Common/pv/qsrvVersionNum.h: ../pv/qsrvVersionNum.h@
$(MKDIR) $(COMMON_DIR)/pv
$(EXPAND_TOOL) $(EXPANDFLAGS) $($@_EXPANDFLAGS) $< $@
qsrv$(DEP): ../O.Common/pv/qsrvVersionNum.h
pvalink$(DEP): ../O.Common/pv/qsrvVersionNum.h
ifdef BASE_3_16
../O.Common/qsrv.dbd: ../qsrv-new.dbd
$(CP) $< $@
else
../O.Common/qsrv.dbd: ../qsrv-old.dbd
$(CP) $< $@
endif
qsrv.dbd$(DEP):
@$(RM) $@
@echo "$(COMMONDEP_TARGET): ../Makefile" > $@
../O.Common/softIocPVA.dbd: ../O.Common/qsrv.dbd
+15 -7
View File
@@ -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);
}
+245
View File
@@ -0,0 +1,245 @@
#include <epicsStdio.h>
#include <dbAccess.h>
#include <dbChannel.h>
#include <dbStaticLib.h>
#include <dbLock.h>
#include <dbEvent.h>
#include <epicsString.h>
#include <epicsVersion.h>
#include <pv/status.h>
#include <pv/bitSet.h>
#include <pv/pvData.h>
#include <pv/anyscalar.h>
#include "pvif.h"
namespace pvd = epics::pvData;
// note that we handle DBF_ENUM differently than in pvif.cpp
static
pvd::ScalarType DBR2PVD(short dbr)
{
switch(dbr) {
#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case DBR_##DBFTYPE: return pvd::pv##PVACODE;
#define CASE_SKIP_BOOL
#define CASE_REAL_INT64
#include "pv/typemap.h"
#undef CASE_SKIP_BOOL
#undef CASE_REAL_INT64
#undef CASE
case DBF_ENUM: return pvd::pvUShort;
case DBF_STRING: return pvd::pvString;
}
throw std::invalid_argument("Unsupported DBR code");
}
long copyPVD2DBF(const pvd::PVField::const_shared_pointer& inraw,
void *outbuf, short outdbf, long *outnReq)
{
long nreq = outnReq ? *outnReq : 1;
if(!inraw || nreq <= 0 || INVALID_DB_REQ(outdbf)) return S_db_errArg;
pvd::ScalarType outpvd = DBR2PVD(outdbf);
pvd::PVField::const_shared_pointer in(inraw);
if(outdbf != DBF_STRING && in->getField()->getType() == pvd::structure) {
// assume NTEnum.
// index to string not requested, so attempt to treat .index as plain integer
in = static_cast<const pvd::PVStructure*>(in.get())->getSubField("index");
if(!in) return S_db_errArg;
}
if(in->getField()->getType() == pvd::structure) {
assert(outdbf == DBF_STRING);
char *outsbuf = (char*)outbuf;
// maybe NTEnum
// try index -> string
const pvd::PVStructure* sin = static_cast<const pvd::PVStructure*>(in.get());
pvd::PVScalar::const_shared_pointer index(sin->getSubField<pvd::PVScalar>("index"));
if(!index) return S_db_badField; // Not NTEnum, don't know how to handle...
// we will have an answer.
if(outnReq)
*outnReq = 1;
pvd::uint16 ival = index->getAs<pvd::uint16>();
pvd::PVStringArray::const_shared_pointer choices(sin->getSubField<pvd::PVStringArray>("choices"));
if(choices) {
pvd::PVStringArray::const_svector strs(choices->view());
if(ival < strs.size()) {
// found it!
const std::string& sval = strs[ival];
size_t slen = std::min(sval.size(), size_t(MAX_STRING_SIZE-1));
memcpy(outbuf, sval.c_str(), slen);
outsbuf[slen] = '\0';
return 0;
}
}
// didn't find it. either no choices or index is out of range
// print numeric index
epicsSnprintf(outsbuf, MAX_STRING_SIZE, "%u", ival);
return 0;
} else if(in->getField()->getType() == pvd::scalarArray) {
const pvd::PVScalarArray* sarr = static_cast<const pvd::PVScalarArray*>(in.get());
pvd::shared_vector<const void> arr;
sarr->getAs(arr);
size_t elemsize = pvd::ScalarTypeFunc::elementSize(arr.original_type());
arr.slice(0, nreq*elemsize);
nreq = arr.size()/elemsize;
if(outdbf == DBF_STRING) {
char *outsbuf = (char*)outbuf;
// allocate a temp buffer of string[], ick...
pvd::shared_vector<std::string> strs(nreq); // alloc
pvd::castUnsafeV(nreq, pvd::pvString, strs.data(), arr.original_type(), arr.data());
for(long i =0; i<nreq; i++, outsbuf += MAX_STRING_SIZE) {
size_t slen = std::min(strs[i].size(), size_t(MAX_STRING_SIZE-1));
memcpy(outsbuf, strs[i].c_str(), slen);
outsbuf[slen] = '\0';
}
} else {
pvd::castUnsafeV(nreq, outpvd, outbuf, arr.original_type(), arr.data());
}
if(outnReq)
*outnReq = nreq;
return 0;
} else if(in->getField()->getType() == pvd::scalar) {
char *outsbuf = (char*)outbuf;
const pvd::PVScalar* sval = static_cast<const pvd::PVScalar*>(in.get());
pvd::AnyScalar val;
sval->getAs(val);
if(outdbf == DBF_STRING && val.type()==pvd::pvString) {
// std::string to char*
size_t len = std::min(val.as<std::string>().size(), size_t(MAX_STRING_SIZE-1));
memcpy(outbuf, val.as<std::string>().c_str(), len);
outsbuf[len] = '\0';
} else if(outdbf == DBF_STRING) {
// non-string to char*
std::string temp;
pvd::castUnsafeV(1, pvd::pvString, &temp, val.type(), val.unsafe());
size_t len = std::min(temp.size(), size_t(MAX_STRING_SIZE-1));
memcpy(outbuf, temp.c_str(), len);
outsbuf[len] = '\0';
} else {
// non-string to any
pvd::castUnsafeV(1, outpvd, outbuf, val.type(), val.unsafe());
}
if(outnReq)
*outnReq = 1;
return 0;
} else {
// struct array or other strangeness which I don't know how to handle
return S_dbLib_badField;
}
}
long copyDBF2PVD(const pvd::shared_vector<const void> &inbuf,
const pvd::PVField::shared_pointer& outraw,
pvd::BitSet& changed,
const pvd::PVStringArray::const_svector &choices)
{
pvd::ScalarType inpvd = inbuf.original_type();
size_t incnt = inbuf.size()/pvd::ScalarTypeFunc::elementSize(inpvd);
if(!outraw) return S_db_errArg;
pvd::PVField::shared_pointer out(outraw);
if(inpvd != pvd::pvString && out->getField()->getType() == pvd::structure) {
// assume NTEnum.
// string to index not requested, so attempt to treat .index as plain integer
out = static_cast<pvd::PVStructure*>(out.get())->getSubField("index");
if(!out) return S_db_errArg;
}
if(out->getField()->getType() == pvd::structure) {
assert(inpvd == pvd::pvString);
if(incnt==0)
return S_db_errArg; // Need at least one string
const pvd::shared_vector<const std::string> insbuf(pvd::static_shared_vector_cast<const std::string>(inbuf));
const std::string& instr(insbuf[0]);
// assume NTEnum
// try string to index, then parse
pvd::PVStructure* sout = static_cast<pvd::PVStructure*>(out.get());
pvd::PVScalar::shared_pointer index(sout->getSubField<pvd::PVScalar>("index"));
if(!index) return S_db_badField; // Not NTEnum, don't know how to handle...
pvd::uint16 result = pvd::uint16(-1);
bool match = false;
for(size_t i=0, N=std::min(size_t(0xffff), choices.size()); i<N; i++) {
if(choices[i] == instr) {
match = true;
result = pvd::uint16(i);
}
}
if(!match) {
// no choice string matched, so try to parse as integer
try{
result = pvd::castUnsafe<pvd::uint16>(instr);
}catch(std::exception&){
return S_db_errArg;
}
}
index->putFrom(result);
out = index;
} else if(out->getField()->getType() == pvd::scalarArray) {
pvd::PVScalarArray* sarr = static_cast<pvd::PVScalarArray*>(out.get());
sarr->putFrom(inbuf);
} else if(out->getField()->getType() == pvd::scalar) {
pvd::PVScalar* sval = static_cast<pvd::PVScalar*>(out.get());
if(incnt==0) return S_db_errArg;
pvd::AnyScalar val(inpvd, inbuf.data());
sval->putFrom(val);
} else {
// struct array or other strangeness which I don't know how to handle
return S_db_badField;
}
changed.set(out->getFieldOffset());
return 0;
}
+163 -105
View File
@@ -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.record()->name, conf.warning.c_str());
recbase = rec.record()->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");
}
}
fprintf(stderr, "%s: warning(s) from info(Q:group, ...\n%s", rec.name(), conf.warning.c_str());
}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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+89
View File
@@ -0,0 +1,89 @@
#ifndef PV_QSRV_H
#define PV_QSRV_H
#include <epicsVersion.h>
#ifndef VERSION_INT
# define VERSION_INT(V,R,M,P) ( ((V)<<24) | ((R)<<16) | ((M)<<8) | (P))
#endif
/* generated header with EPICS_QSRV_*_VERSION macros */
# include <pv/qsrvVersionNum.h>
#define QSRV_VERSION_INT VERSION_INT(EPICS_QSRV_MAJOR_VERSION, EPICS_QSRV_MINOR_VERSION, EPICS_QSRV_MAINTENANCE_VERSION, !(EPICS_QSRV_DEVELOPMENT_FLAG))
#define QSRV_ABI_VERSION_INT VERSION_INT(EPICS_QSRV_ABI_MAJOR_VERSION, EPICS_QSRV_ABI_MINOR_VERSION, 0, 0)
#if defined(QSRV_API_BUILDING) && defined(epicsExportSharedSymbols)
# error Use QSRV_API or shareLib.h not both
#endif
#if defined(_WIN32) || defined(__CYGWIN__)
# if defined(QSRV_API_BUILDING) && defined(EPICS_BUILD_DLL)
/* building library as dll */
# define QSRV_API __declspec(dllexport)
# elif !defined(QSRV_API_BUILDING) && defined(EPICS_CALL_DLL)
/* calling library in dll form */
# define QSRV_API __declspec(dllimport)
# endif
#elif __GNUC__ >= 4
# define QSRV_API __attribute__ ((visibility("default")))
#endif
#ifndef QSRV_API
# define QSRV_API
#endif
#ifdef __cplusplus
extern "C" {
#endif
struct link; /* aka. DBLINK from link.h */
/** returns QSRV_VERSION_INT captured at compilation time */
QSRV_API unsigned qsrvVersion(void);
/** returns QSRV_ABI_VERSION_INT captured at compilation time */
QSRV_API unsigned qsrvABIVersion(void);
QSRV_API
long dbLoadGroup(const char* fname);
QSRV_API void testqsrvWaitForLinkEvent(struct link *plink);
/** Call before testIocShutdownOk()
@code
testdbPrepare();
...
testIocInitOk();
...
testqsrvShutdownOk();
testIocShutdownOk();
testqsrvCleanup();
testdbCleanup();
@endcode
*/
QSRV_API void testqsrvShutdownOk(void);
/** Call after testIocShutdownOk() and before testdbCleanup()
@code
testdbPrepare();
...
testIocInitOk();
...
testqsrvShutdownOk();
testIocShutdownOk();
testqsrvCleanup();
testdbCleanup();
@endcode
*/
QSRV_API void testqsrvCleanup(void);
#ifdef __cplusplus
}
#endif
#endif /* PV_QSRV_H */
+9
View File
@@ -0,0 +1,9 @@
#ifndef PV_QSRV_H
# error qsrvVersionNum.h should never be included directly. Include <pv/qsrv.h>
#endif
#define EPICS_QSRV_MAJOR_VERSION @EPICS_QSRV_MAJOR_VERSION@
#define EPICS_QSRV_MINOR_VERSION @EPICS_QSRV_MINOR_VERSION@
#define EPICS_QSRV_MAINTENANCE_VERSION @EPICS_QSRV_MAINTENANCE_VERSION@
#define EPICS_QSRV_DEVELOPMENT_FLAG @EPICS_QSRV_DEVELOPMENT_FLAG@
#define EPICS_QSRV_ABI_MAJOR_VERSION @EPICS_QSRV_ABI_MAJOR_VERSION@
#define EPICS_QSRV_ABI_MINOR_VERSION @EPICS_QSRV_ABI_MINOR_VERSION@
+260 -753
View File
File diff suppressed because it is too large Load Diff
+159 -247
View File
@@ -10,25 +10,53 @@
#include <dbCommon.h>
#include <dbLink.h>
#include <dbScan.h>
#include <epicsExport.h>
#include <errlog.h>
#include <initHooks.h>
#include <alarm.h>
#include <epicsExit.h>
#include <epicsAtomic.h>
#include <epicsThreadPool.h>
#include <link.h>
#include <dbJLink.h>
#include <errlog.h>
#include <epicsThread.h>
#include <epicsMutex.h>
#include <epicsEvent.h>
#include <dbChannel.h>
#include <dbStaticLib.h>
#include <dbLock.h>
#include <dbEvent.h>
#include <epicsVersion.h>
#include <pv/pvAccess.h>
#include <pv/clientFactory.h>
#include <pv/status.h>
#include <pv/bitSet.h>
#include <pv/pvData.h>
#include <pva/client.h>
#include <pv/anyscalar.h>
#include <pv/thread.h>
#include <pv/lock.h>
#include <pv/iocshelper.h>
#include <pv/sharedPtr.h>
#include "helper.h"
#include "iocshelper.h"
#include "pvif.h"
#include "tpool.h"
extern int pvaLinkDebug;
extern int pvaLinkIsolate;
extern "C" {
QSRV_API extern int pvaLinkDebug;
QSRV_API extern int pvaLinkIsolate;
QSRV_API extern int pvaLinkNWorkers;
}
#if 0
# define TRACE(X) std::cerr<<"PVAL "<<__func__<<" " X <<"\n"
#else
# define TRACE(X) do {} while(0)
#endif
// pvaLink and pvaLinkChannel have ->debug
#define DEBUG(OBJ, X) do{ if((OBJ)->debug) std::cout X<<"\n"; }while(0)
namespace pvalink {
@@ -41,289 +69,173 @@ typedef epicsGuardRelease<pvd::Mutex> UnGuard;
struct pvaLink;
struct pvaLinkChannel;
struct pvaGlobal_t {
pva::ChannelProvider::shared_pointer provider;
extern lset pva_lset;
extern jlif lsetPVA;
pvd::StructureConstPtr reqtype;
pvd::PVDataCreatePtr create;
struct pvaLinkConfig : public jlink
{
// configuration, output of jlif parsing
//! Channel (aka PV) name string
std::string channelName;
//! sub-field within addressed PVStructure
std::string fieldName;
size_t queueSize;
enum pp_t {
NPP,
Default, // for put() only. For monitor, treated as NPP
PP, // for put() only, For monitor, treated as NPP
CP, // for monitor only, put treats as pp
CPP, // for monitor only, put treats as pp
} pp;
enum ms_t {
NMS,
MS,
MSI,
} ms;
bool defer, pipeline, time, retry, local, always;
int monorder;
// internals used by jlif parsing
std::string jkey;
pvaLinkConfig();
virtual ~pvaLinkConfig();
};
struct pvaGlobal_t {
pvac::ClientProvider provider_local,
provider_remote;
const pvd::PVDataCreatePtr create;
WorkQueue queue;
pvd::Mutex lock;
struct Scan {
// the PVA channel which triggered this scan
std::tr1::weak_ptr<pvaLinkChannel> chan;
bool usecached;
Scan() :usecached(false) {}
};
bool running; // set after dbEvent is initialized and safe to use
epicsThreadPrivate<Scan> scanmagic;
epicsThreadPool *scanpool;
typedef std::map<std::string, std::tr1::shared_ptr<pvaLinkChannel> > channels_t;
// a tuple of channel name and printed pvRequest (or Monitor)
typedef std::pair<std::string, std::string> channels_key_t;
// pvaLinkChannel dtor prunes dead entires
typedef std::map<channels_key_t, std::tr1::weak_ptr<pvaLinkChannel> > channels_t;
// Cache of active Channels (really about caching Monitor)
channels_t channels;
std::tr1::shared_ptr<pvaLinkChannel> connect(const char *name);
pvaGlobal_t();
~pvaGlobal_t()
{
provider->destroy();
epicsThreadPoolDestroy(scanpool);
}
~pvaGlobal_t();
};
extern pvaGlobal_t *pvaGlobal;
struct pvaLinkChannel : public pva::ChannelRequester, pva::MonitorRequester,
std::tr1::enable_shared_from_this<pvaLinkChannel>
struct pvaLinkChannel : public pvac::ClientChannel::MonitorCallback,
public pvac::ClientChannel::PutCallback,
public epicsThreadRunable,
public std::tr1::enable_shared_from_this<pvaLinkChannel>
{
const std::string name;
const pvaGlobal_t::channels_key_t key; // tuple of (channelName, pvRequest key)
const pvd::PVStructure::const_shared_pointer pvRequest; // used with monitor
static size_t refs;
typedef std::set<pvaLink*> links_t;
links_t links;
static size_t num_instances;
pvd::Mutex lock;
epicsEvent run_done; // used by testing code
pva::Channel::shared_pointer chan;
pvac::ClientChannel chan;
pvac::Monitor op_mon;
pvac::Operation op_put;
pva::Monitor::shared_pointer chanmon;
//pva::ChannelPut::shared_pointer chanput;
std::string providerName;
size_t num_disconnect, num_type_change;
bool connected;
bool connected_latched; // connection status at the run()
bool isatomic;
bool queued; // added to WorkQueue
bool debug; // set if any jlink::debug is set
std::tr1::shared_ptr<const void> previous_root;
pvd::PVStructurePtr lastval;
pvd::PVScalarPtr isatomic;
struct LinkSort {
bool operator()(const pvaLink *L, const pvaLink *R) const;
};
epicsJob *scanjob;
std::tr1::shared_ptr<pvaLinkChannel> scanself; // create ref loop while scan is queued
bool scanatomic;
typedef std::set<pvaLink*, LinkSort> links_t;
pvaLinkChannel(const char *name)
:name(name)
,scanjob(epicsJobCreate(pvaGlobal->scanpool, &pvaLinkChannel::scan, this))
,scanatomic(false)
{
if(!scanjob)
throw std::runtime_error("failed to create job for pvaLink");
epics::atomic::increment(refs);
}
virtual ~pvaLinkChannel() {
Guard G(lock);
assert(links.empty());
epicsJobDestroy(scanjob);
scanjob = NULL;
epics::atomic::decrement(refs);
std::cerr<<"pvaLinkChannel: destroy "<<name<<"\n";
}
// list of currently attached links. maintained by pvaLink ctor/dtor
// TODO: sort by PHAS
links_t links;
void doConnect() {
// TODO: local PVA?
Guard G(lock);
chan = pvaGlobal->provider->createChannel(name, shared_from_this());
channelStateChange(chan, chan->getConnectionState());
}
void doClose() {
Guard G(lock);
errlogPrintf("pvaLink closing %s\n", name.c_str());
channelStateChange(chan, pva::Channel::DESTROYED);
chan->destroy();
chan.reset();
std::cerr<<"pvaLink: channel destroy "<<name<<"\n";
}
// set when 'links' is modified to trigger re-compute of record scan list
bool links_changed;
void triggerProc(bool atomic=false, bool force=false);
pvaLinkChannel(const pvaGlobal_t::channels_key_t& key, const epics::pvData::PVStructure::const_shared_pointer &pvRequest);
virtual ~pvaLinkChannel();
static void scan(void* arg, epicsJobMode mode);
void open();
void put(bool force=false); // begin Put op.
virtual std::string getRequesterName() { return "pvaLink"; }
virtual void message(std::string const & message, pva::MessageType messageType)
{
errlogPrintf("%s pvaLink \"%s\": %s\n",
pvd::getMessageTypeName(messageType).c_str(),
name.c_str(),
message.c_str());
}
// pvac::ClientChanel::MonitorCallback
virtual void monitorEvent(const pvac::MonitorEvent& evt) OVERRIDE FINAL;
virtual void channelCreated(const epics::pvData::Status& status, pva::Channel::shared_pointer const & channel)
{
if(!status.isSuccess()) {
errlogPrintf("pvaLink create fails %s: %s\n", name.c_str(), status.getMessage().c_str());
return;
}
Guard G(lock);
//assert(chan==channel); // may be called before createChannel() returns
chan = channel;
}
// pvac::ClientChanel::PutCallback
virtual void putBuild(const epics::pvData::StructureConstPtr& build, pvac::ClientChannel::PutCallback::Args& args) OVERRIDE FINAL;
virtual void putDone(const pvac::PutEvent& evt) OVERRIDE FINAL;
private:
virtual void run() OVERRIDE FINAL;
void run_dbProcess(size_t idx); // idx is index in scan_records
virtual void channelStateChange(pva::Channel::shared_pointer const & channel, pva::Channel::ConnectionState connectionState);
// ==== Treat remaining as local to run()
virtual void monitorConnect(pvd::Status const & status,
pva::Monitor::shared_pointer const & monitor,
pvd::StructureConstPtr const & structure);
virtual void monitorEvent(pva::Monitor::shared_pointer const & monitor);
virtual void unlisten(pva::Monitor::shared_pointer const & monitor)
{
// what to do??
}
std::vector<dbCommon*> scan_records;
std::vector<bool> scan_check_passive;
std::vector<epics::pvData::BitSet> scan_changed;
DBManyLock atomic_lock;
};
struct pvaLink : public jlink
struct pvaLink : public pvaLinkConfig
{
static size_t refs;
static size_t num_instances;
bool alive; // attempt to catch some use after free
DBLINK * plink; // may be NULL
unsigned linkmods;
unsigned parse_level;
std::string name, field;
const pva::Channel::shared_pointer chan;
bool alive; // attempt to catch some use after free
std::tr1::shared_ptr<pvaLinkChannel> lchan;
pvd::PVScalarPtr valueS;
pvd::PVScalarArray::shared_pointer valueA;
pvd::PVScalar::shared_pointer sevr, sec, nsec;
pvd::ScalarType etype;
bool used_scratch, used_queue;
pvd::shared_vector<const void> put_scratch, put_queue;
struct Value {
bool valid;
bool scalar;
pvd::ScalarType etype;
pvd::shared_vector<const void> valueA;
dbrbuf valueS;
epicsUInt16 sevr;
epicsTimeStamp time;
Value() :valid(false) {}
void clear() {
valid = false;
valueA.clear();
}
};
// cached fields from channel op_mon
// updated in onTypeChange()
epics::pvData::PVField::const_shared_pointer fld_value;
epics::pvData::PVScalar::const_shared_pointer fld_severity,
fld_seconds,
fld_nanoseconds;
epics::pvData::PVStructure::const_shared_pointer fld_display,
fld_control,
fld_valueAlarm;
epics::pvData::BitSet proc_changed;
Value atomcache;
// cached snapshot of alarm and timestamp
// captured in pvaGetValue().
// we choose not to ensure consistency with display/control meta-data
epicsTimeStamp snap_time;
short snap_severity;
pvaLink()
:plink(0)
,linkmods(0)
,parse_level(0)
,alive(true)
{
epics::atomic::increment(refs);
//TODO: valgrind tells me these aren't initialized by Base, but probably should be.
parseDepth = 0;
parent = 0;
}
pvaLink();
virtual ~pvaLink();
void open()
{
if(this->name.empty())
throw std::logic_error("open() w/o target PV name");
this->name = name;
//TODO: how to distinguish "record.FLD" from pva "channel.subfield"?
size_t dot = this->name.find_first_of('.');
if(dot!=this->name.npos) {
field = this->name.substr(dot+1);
this->name = this->name.substr(0, dot);
}
lchan = pvaGlobal->connect(this->name.c_str());
Guard G(lchan->lock);
lchan->links.insert(this);
if(lchan->lastval)
attach();
}
~pvaLink()
{
alive = false;
if(lchan) { // may be NULL if parsing fails
Guard G(lchan->lock);
detach();
lchan->links.erase(this);
if(lchan->links.empty()) {
pvaGlobal->channels.erase(lchan->name);
lchan->doClose();
}
}
epics::atomic::decrement(refs);
}
// returns pvRequest to be used with monitor
pvd::PVStructurePtr makeRequest();
void detach()
{
valueS.reset();
valueA.reset();
sevr.reset();
sec.reset();
nsec.reset();
}
bool valid() const;
bool attach()
{
pvd::PVStructurePtr base(lchan->lastval);
// fetch a sub-sub-field of the top monitored field.
pvd::PVField::const_shared_pointer getSubField(const char *name);
if(!field.empty())
base = base->getSubField<pvd::PVStructure>(field);
if(!base) {
errlogPrintf("pvaLink not %s%c%s\n", name.c_str(), field.empty() ? ' ' : '.', field.c_str());
return false;
}
pvd::PVFieldPtr value(base->getSubField("value"));
switch(value->getField()->getType())
{
case pvd::scalar:
valueS = std::tr1::static_pointer_cast<pvd::PVScalar>(value);
etype = valueS->getScalar()->getScalarType();
break;
case pvd::scalarArray:
valueA = std::tr1::static_pointer_cast<pvd::PVScalarArray>(value);
etype = valueA->getScalarArray()->getElementType();
break;
default:
errlogPrintf("pvaLink not .value : %s%c%s\n", name.c_str(), field.empty() ? ' ' : '.', field.c_str());
return false;
}
sevr = base->getSubField<pvd::PVScalar>("alarm.severity");
sec = base->getSubField<pvd::PVScalar>("timeStamp.secondsPastEpoch");
nsec = base->getSubField<pvd::PVScalar>("timeStamp.nanoseconds");
return true;
}
void get(Value& v)
{
if(valueA) {
valueA->getAs<const void>(v.valueA);
v.etype = v.valueA.original_type();
v.scalar = false;
} else if(valueS) {
switch(etype) {
#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case pvd::pv ## PVACODE: v.valueS.dbf_##DBFTYPE = valueS->getAs<PVATYPE>(); break;
#define CASE_SQUEEZE_INT64
#include "pvatypemap.h"
#undef CASE_SQUEEZE_INT64
#undef CASE
case pvd::pvString: {
strncpy(v.valueS.dbf_STRING, valueS->getAs<std::string>().c_str(), sizeof(v.valueS.dbf_STRING));
v.valueS.dbf_STRING[sizeof(v.valueS.dbf_STRING)-1] = '\0';
}
break;
default:
throw std::runtime_error("putValue unsupported DBR code");
}
v.etype = etype;
v.scalar = true;
}
v.sevr = sevr->getAs<epicsUInt16>();
v.time.secPastEpoch = sec->getAs<epicsUInt32>()-POSIX_TIME_AT_EPICS_EPOCH;
v.time.nsec = nsec->getAs<epicsUInt32>();
v.valid = true;
}
void onDisconnect();
void onTypeChange();
};
+382
View File
@@ -0,0 +1,382 @@
#include <alarm.h>
#include <pv/reftrack.h>
#include "pvalink.h"
int pvaLinkNWorkers = 1;
namespace pvalink {
pvaGlobal_t *pvaGlobal;
pvaGlobal_t::pvaGlobal_t()
:create(pvd::getPVDataCreate())
,queue("PVAL")
,running(false)
{
// worker should be above PVA worker priority?
queue.start(std::max(1, pvaLinkNWorkers), epicsThreadPriorityMedium);
}
pvaGlobal_t::~pvaGlobal_t()
{
}
size_t pvaLinkChannel::num_instances;
size_t pvaLink::num_instances;
bool pvaLinkChannel::LinkSort::operator()(const pvaLink *L, const pvaLink *R) const {
if(L->monorder==R->monorder)
return L < R;
return L->monorder < R->monorder;
}
// being called with pvaGlobal::lock held
pvaLinkChannel::pvaLinkChannel(const pvaGlobal_t::channels_key_t &key, const pvd::PVStructure::const_shared_pointer& pvRequest)
:key(key)
,pvRequest(pvRequest)
,num_disconnect(0u)
,num_type_change(0u)
,connected(false)
,connected_latched(false)
,isatomic(false)
,queued(false)
,debug(false)
,links_changed(false)
{}
pvaLinkChannel::~pvaLinkChannel() {
{
Guard G(pvaGlobal->lock);
pvaGlobal->channels.erase(key);
}
Guard G(lock);
assert(links.empty());
REFTRACE_DECREMENT(num_instances);
}
void pvaLinkChannel::open()
{
Guard G(lock);
try {
chan = pvaGlobal->provider_local.connect(key.first);
DEBUG(this, <<key.first<<" OPEN Local");
providerName = pvaGlobal->provider_local.name();
} catch(std::exception& e){
// The PDBProvider doesn't have a way to communicate to us
// whether this is an invalid record or group name,
// or if this is some sort of internal error.
// So we are forced to assume it is an invalid name.
DEBUG(this, <<key.first<<" OPEN Not local "<<e.what());
}
if(!pvaLinkIsolate && !chan) {
chan = pvaGlobal->provider_remote.connect(key.first);
DEBUG(this, <<key.first<<" OPEN Remote ");
providerName = pvaGlobal->provider_remote.name();
}
op_mon = chan.monitor(this, pvRequest);
REFTRACE_INCREMENT(num_instances);
}
static
pvd::StructureConstPtr putRequestType = pvd::getFieldCreate()->createFieldBuilder()
->addNestedStructure("field")
->endNested()
->addNestedStructure("record")
->addNestedStructure("_options")
->add("block", pvd::pvBoolean)
->add("process", pvd::pvString) // "true", "false", or "passive"
->endNested()
->endNested()
->createStructure();
// call with channel lock held
void pvaLinkChannel::put(bool force)
{
pvd::PVStructurePtr pvReq(pvd::getPVDataCreate()->createPVStructure(putRequestType));
pvReq->getSubFieldT<pvd::PVBoolean>("record._options.block")->put(false); // TODO: some way to expose completion...
unsigned reqProcess = 0;
bool doit = force;
for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it)
{
pvaLink *link = *it;
if(!link->used_scratch) continue;
pvd::shared_vector<const void> temp;
temp.swap(link->put_scratch);
link->used_scratch = false;
temp.swap(link->put_queue);
link->used_queue = true;
doit = true;
switch(link->pp) {
case pvaLink::NPP:
reqProcess |= 1;
break;
case pvaLink::Default:
break;
case pvaLink::PP:
case pvaLink::CP:
case pvaLink::CPP:
reqProcess |= 2;
break;
}
}
/* By default, use remote default (passive).
* Request processing, or not, if any link asks.
* Prefer PP over NPP if both are specified.
*
* TODO: per field granularity?
*/
const char *proc = "passive";
if((reqProcess&2) || force) {
proc = "true";
} else if(reqProcess&1) {
proc = "false";
}
pvReq->getSubFieldT<pvd::PVString>("record._options.process")->put(proc);
DEBUG(this, <<key.first<<"Start put "<<doit);
if(doit) {
// start net Put, cancels in-progress put
op_put = chan.put(this, pvReq);
}
}
void pvaLinkChannel::putBuild(const epics::pvData::StructureConstPtr& build, pvac::ClientChannel::PutCallback::Args& args)
{
Guard G(lock);
pvd::PVStructurePtr top(pvaGlobal->create->createPVStructure(build));
for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it)
{
pvaLink *link = *it;
if(!link->used_queue) continue;
link->used_queue = false; // clear early so unexpected exception won't get us in a retry loop
pvd::PVFieldPtr value(link->fieldName.empty() ? pvd::PVFieldPtr(top) : top->getSubField(link->fieldName));
if(value && value->getField()->getType()==pvd::structure) {
// maybe drill into NTScalar et al.
pvd::PVFieldPtr sub(static_cast<pvd::PVStructure*>(value.get())->getSubField("value"));
if(sub)
value.swap(sub);
}
if(!value) continue; // TODO: how to signal error?
pvd::PVStringArray::const_svector choices; // TODO populate from op_mon
DEBUG(this, <<key.first<<" <- "<<value->getFullName());
copyDBF2PVD(link->put_queue, value, args.tosend, choices);
link->put_queue.clear();
}
DEBUG(this, <<key.first<<" Put built");
args.root = top;
}
void pvaLinkChannel::putDone(const pvac::PutEvent& evt)
{
if(evt.event==pvac::PutEvent::Fail) {
errlogPrintf("%s PVA link put ERROR: %s\n", key.first.c_str(), evt.message.c_str());
}
Guard G(lock);
DEBUG(this, <<key.first<<" Put result "<<evt.event);
op_put = pvac::Operation();
if(evt.event==pvac::PutEvent::Success) {
// see if we need start a queue'd put
put();
}
}
void pvaLinkChannel::monitorEvent(const pvac::MonitorEvent& evt)
{
bool queue = false;
{
DEBUG(this, <<key.first<<" EVENT "<<evt.event);
Guard G(lock);
switch(evt.event) {
case pvac::MonitorEvent::Disconnect:
case pvac::MonitorEvent::Data:
connected = evt.event == pvac::MonitorEvent::Data;
queue = true;
break;
case pvac::MonitorEvent::Cancel:
break; // no-op
case pvac::MonitorEvent::Fail:
connected = false;
queue = true;
errlogPrintf("%s: PVA link monitor ERROR: %s\n", chan.name().c_str(), evt.message.c_str());
break;
}
if(queued)
return; // already scheduled
queued = queue;
}
if(queue) {
pvaGlobal->queue.add(shared_from_this());
}
}
// the work in calling dbProcess() which is common to
// both dbScanLock() and dbScanLockMany()
void pvaLinkChannel::run_dbProcess(size_t idx)
{
dbCommon *precord = scan_records[idx];
if(scan_check_passive[idx] && precord->scan!=0) {
return;
} else if(connected_latched && !op_mon.changed.logical_and(scan_changed[idx])) {
return;
} else if (precord->pact) {
if (precord->tpro)
printf("%s: Active %s\n",
epicsThreadGetNameSelf(), precord->name);
precord->rpro = TRUE;
}
dbProcess(precord);
}
// Running from global WorkQueue thread
void pvaLinkChannel::run()
{
bool requeue = false;
{
Guard G(lock);
queued = false;
connected_latched = connected;
// pop next update from monitor queue.
// still under lock to safeguard concurrent calls to lset functions
if(connected && !op_mon.poll()) {
DEBUG(this, <<key.first<<" RUN "<<"empty");
run_done.signal();
return; // monitor queue is empty, nothing more to do here
}
DEBUG(this, <<key.first<<" RUN "<<(connected_latched?"connected":"disconnected"));
assert(!connected || !!op_mon.root);
if(!connected) {
num_disconnect++;
// cancel pending put operations
op_put = pvac::Operation();
for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it)
{
pvaLink *link = *it;
link->onDisconnect();
}
// Don't clear previous_root on disconnect.
// We will usually re-connect with the same type,
// and may get back the same PVStructure.
} else if(previous_root.get() != (const void*)op_mon.root.get()) {
num_type_change++;
for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it)
{
pvaLink *link = *it;
link->onTypeChange();
}
previous_root = std::tr1::static_pointer_cast<const void>(op_mon.root);
}
// at this point we know we will re-queue, but not immediately
// so an expected error won't get us stuck in a tight loop.
requeue = queued = connected_latched;
if(links_changed) {
// a link has been added or removed since the last update.
// rebuild our cached list of records to (maybe) process.
scan_records.clear();
scan_check_passive.clear();
scan_changed.clear();
for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it)
{
pvaLink *link = *it;
assert(link && link->alive);
if(!link->plink) continue;
// NPP and none/Default don't scan
// PP, CP, and CPP do scan
// PP and CPP only if SCAN=Passive
if(link->pp != pvaLink::PP && link->pp != pvaLink::CPP && link->pp != pvaLink::CP)
continue;
scan_records.push_back(link->plink->precord);
scan_check_passive.push_back(link->pp != pvaLink::CP);
scan_changed.push_back(link->proc_changed);
}
DBManyLock ML(scan_records);
atomic_lock.swap(ML);
links_changed = false;
}
}
if(scan_records.empty()) {
// Nothing to do, so don't bother locking
} else if(isatomic && scan_records.size() > 1u) {
DBManyLocker L(atomic_lock);
for(size_t i=0, N=scan_records.size(); i<N; i++) {
run_dbProcess(i);
}
} else {
for(size_t i=0, N=scan_records.size(); i<N; i++) {
DBScanLocker L(scan_records[i]);
run_dbProcess(i);
}
}
if(requeue) {
// re-queue until monitor queue is empty
pvaGlobal->queue.add(shared_from_this());
} else {
run_done.signal();
}
}
} // namespace pvalink
+321
View File
@@ -0,0 +1,321 @@
#include <sstream>
#include <epicsStdio.h> // redirects stdout/stderr
#include "pvalink.h"
namespace pvalink {
pvaLinkConfig::pvaLinkConfig()
:queueSize(4)
,pp(Default)
,ms(NMS)
,defer(false)
,pipeline(false)
,time(false)
,retry(false)
,local(false)
,always(false)
,monorder(0)
{}
pvaLinkConfig::~pvaLinkConfig() {}
}
namespace {
using namespace pvalink;
/* link options.
*
* "pvname" # short-hand, sets PV name only
*
* {
* "pv":"name",
* "field":"blah.foo",
* "Q":5,
* "pipeline":false,
* "proc":true, // false, true, none, "", "NPP", "PP", "CP", "CPP"
* "sevr":true, // false, true, "NMS", "MS", "MSI", "MSS"
* "time":true, // false, true
* "monorder":#,// order of processing during CP scan
* "defer":true,// whether to immediately start Put, or only queue value to be sent
* "retry":true,// queue Put while disconnected, and retry on connect
* "always":true,// CP/CPP updates always process a like, even if its input field hasn't changed
* "local":false,// Require local channel
* }
*/
jlink* pva_alloc_jlink(short dbr)
{
try {
TRACE();
return new pvaLink;
}catch(std::exception& e){
errlogPrintf("Error allocating pva link: %s\n", e.what());
return NULL;
}
}
#define TRY pvaLinkConfig *pvt = static_cast<pvaLinkConfig*>(pjlink); (void)pvt; try
#define CATCH(RET) catch(std::exception& e){ \
errlogPrintf("Error in %s link: %s\n", __FUNCTION__, e.what()); \
return RET; }
void pva_free_jlink(jlink *pjlink)
{
TRY {
TRACE();
delete pvt;
}catch(std::exception& e){
errlogPrintf("Error freeing pva link: %s\n", e.what());
}
}
jlif_result pva_parse_null(jlink *pjlink)
{
TRY {
TRACE(<<pvt->jkey<<" ");
if(pvt->parseDepth!=1) {
// ignore
} else if(pvt->jkey == "proc") {
pvt->pp = pvaLinkConfig::Default;
} else if(pvt->jkey == "sevr") {
pvt->ms = pvaLinkConfig::NMS;
} else if(pvt->jkey == "local") {
pvt->local = false; // alias for local:false
} else if(pvt->debug) {
printf("pva link parsing unknown none depth=%u key=\"%s\"\n",
pvt->parseDepth, pvt->jkey.c_str());
}
pvt->jkey.clear();
return jlif_continue;
}CATCH(jlif_stop)
}
jlif_result pva_parse_bool(jlink *pjlink, int val)
{
TRY {
TRACE(<<pvt->jkey<<" "<<(val?"true":"false"));
if(pvt->parseDepth!=1) {
// ignore
} else if(pvt->jkey == "proc") {
pvt->pp = val ? pvaLinkConfig::PP : pvaLinkConfig::NPP;
} else if(pvt->jkey == "sevr") {
pvt->ms = val ? pvaLinkConfig::MS : pvaLinkConfig::NMS;
} else if(pvt->jkey == "defer") {
pvt->defer = !!val;
} else if(pvt->jkey == "pipeline") {
pvt->pipeline = !!val;
} else if(pvt->jkey == "time") {
pvt->time = !!val;
} else if(pvt->jkey == "retry") {
pvt->retry = !!val;
} else if(pvt->jkey == "local") {
pvt->local = !!val;
} else if(pvt->jkey == "always") {
pvt->always = !!val;
} else if(pvt->debug) {
printf("pva link parsing unknown integer depth=%u key=\"%s\" value=%s\n",
pvt->parseDepth, pvt->jkey.c_str(), val ? "true" : "false");
}
pvt->jkey.clear();
return jlif_continue;
}CATCH(jlif_stop)
}
jlif_result pva_parse_integer(jlink *pjlink, long long val)
{
TRY {
TRACE(<<pvt->jkey<<" "<<val);
if(pvt->parseDepth!=1) {
// ignore
} else if(pvt->jkey == "Q") {
pvt->queueSize = val < 1 ? 1 : size_t(val);
} else if(pvt->jkey == "monorder") {
pvt->monorder = std::max(-1024, std::min(int(val), 1024));
} else if(pvt->debug) {
printf("pva link parsing unknown integer depth=%u key=\"%s\" value=%lld\n",
pvt->parseDepth, pvt->jkey.c_str(), val);
}
pvt->jkey.clear();
return jlif_continue;
}CATCH(jlif_stop)
}
jlif_result pva_parse_string(jlink *pjlink, const char *val, size_t len)
{
TRY{
std::string sval(val, len);
TRACE(<<pvt->jkey<<" "<<sval);
if(pvt->parseDepth==0 || (pvt->parseDepth==1 && pvt->jkey=="pv")) {
pvt->channelName = sval;
} else if(pvt->parseDepth > 1) {
// ignore
} else if(pvt->jkey=="field") {
pvt->fieldName = sval;
} else if(pvt->jkey=="proc") {
if(sval.empty()) {
pvt->pp = pvaLinkConfig::Default;
} else if(sval=="CP") {
pvt->pp = pvaLinkConfig::CP;
} else if(sval=="CPP") {
pvt->pp = pvaLinkConfig::CPP;
} else if(sval=="PP") {
pvt->pp = pvaLinkConfig::PP;
} else if(sval=="NPP") {
pvt->pp = pvaLinkConfig::NPP;
} else if(pvt->debug) {
printf("pva link parsing unknown proc depth=%u key=\"%s\" value=\"%s\"\n",
pvt->parseDepth, pvt->jkey.c_str(), sval.c_str());
}
} else if(pvt->jkey=="sevr") {
if(sval=="NMS") {
pvt->ms = pvaLinkConfig::NMS;
} else if(sval=="MS") {
pvt->ms = pvaLinkConfig::MS;
} else if(sval=="MSI") {
pvt->ms = pvaLinkConfig::MSI;
} else if(sval=="MSS") {
// not sure how to handle mapping severity for MSS.
// leave room for this to happen compatibly later by
// handling as alias for MS until then.
pvt->ms = pvaLinkConfig::MS;
} else if(pvt->debug) {
printf("pva link parsing unknown sevr depth=%u key=\"%s\" value=\"%s\"\n",
pvt->parseDepth, pvt->jkey.c_str(), sval.c_str());
}
} else if(pvt->debug) {
printf("pva link parsing unknown string depth=%u key=\"%s\" value=\"%s\"\n",
pvt->parseDepth, pvt->jkey.c_str(), sval.c_str());
}
pvt->jkey.clear();
return jlif_continue;
}CATCH(jlif_stop)
}
jlif_key_result pva_parse_start_map(jlink *pjlink)
{
TRY {
TRACE();
return jlif_key_continue;
}CATCH(jlif_key_stop)
}
jlif_result pva_parse_key_map(jlink *pjlink, const char *key, size_t len)
{
TRY {
std::string sval(key, len);
TRACE(<<sval);
pvt->jkey = sval;
return jlif_continue;
}CATCH(jlif_stop)
}
jlif_result pva_parse_end_map(jlink *pjlink)
{
TRY {
TRACE();
return jlif_continue;
}CATCH(jlif_stop)
}
struct lset* pva_get_lset(const jlink *pjlink)
{
TRACE();
return &pva_lset;
}
void pva_report(const jlink *rpjlink, int lvl, int indent)
{
const pvaLink *pval = static_cast<const pvaLink*>(rpjlink);
try {
(void)pval;
printf("%*s'pva': %s", indent, "", pval->channelName.c_str());
if(!pval->fieldName.empty())
printf("|.%s", pval->fieldName.c_str());
switch(pval->pp) {
case pvaLinkConfig::NPP: printf(" NPP"); break;
case pvaLinkConfig::Default: printf(" Def"); break;
case pvaLinkConfig::PP: printf(" PP"); break;
case pvaLinkConfig::CP: printf(" CP"); break;
case pvaLinkConfig::CPP: printf(" CPP"); break;
}
switch(pval->ms) {
case pvaLinkConfig::NMS: printf(" NMS"); break;
case pvaLinkConfig::MS: printf(" MS"); break;
case pvaLinkConfig::MSI: printf(" MSI"); break;
}
if(lvl>0) {
printf(" Q=%u pipe=%c defer=%c time=%c retry=%c morder=%d",
unsigned(pval->queueSize),
pval->pipeline ? 'T' : 'F',
pval->defer ? 'T' : 'F',
pval->time ? 'T' : 'F',
pval->retry ? 'T' : 'F',
pval->monorder);
}
if(pval->lchan) {
// after open()
Guard G(pval->lchan->lock);
printf(" conn=%c", pval->lchan->connected ? 'T' : 'F');
if(pval->lchan->op_put.valid()) {
printf(" Put");
}
if(lvl>0) {
printf(" #disconn=%zu prov=%s", pval->lchan->num_disconnect, pval->lchan->providerName.c_str());
}
if(lvl>1) {
printf(" inprog=%c",
pval->lchan->queued?'T':'F');
}
if(lvl>5) {
std::ostringstream strm;
pval->lchan->chan.show(strm);
printf("\n%*s CH: %s", indent, "", strm.str().c_str());
}
} else {
printf(" No Channel");
}
printf("\n");
}CATCH()
}
} //namespace
namespace pvalink {
jlif lsetPVA = {
"pva",
&pva_alloc_jlink,
&pva_free_jlink,
&pva_parse_null,
&pva_parse_bool,
&pva_parse_integer,
NULL,
&pva_parse_string,
&pva_parse_start_map,
&pva_parse_key_map,
&pva_parse_end_map,
NULL,
NULL,
NULL,
&pva_get_lset,
&pva_report,
NULL
};
} //namespace pvalink
+155
View File
@@ -0,0 +1,155 @@
#include <pv/reftrack.h>
#include <alarm.h>
#include "pvalink.h"
namespace pvalink {
pvaLink::pvaLink()
:alive(true)
,plink(0)
,used_scratch(false)
,used_queue(false)
{
REFTRACE_INCREMENT(num_instances);
snap_severity = INVALID_ALARM;
snap_time.secPastEpoch = 0;
snap_time.nsec = 0;
//TODO: valgrind tells me these aren't initialized by Base, but probably should be.
parseDepth = 0;
parent = 0;
}
pvaLink::~pvaLink()
{
alive = false;
if(lchan) { // may be NULL if parsing fails
Guard G(lchan->lock);
lchan->links.erase(this);
lchan->links_changed = true;
bool new_debug = false;
for(pvaLinkChannel::links_t::const_iterator it(lchan->links.begin()), end(lchan->links.end())
; it!=end; ++it)
{
const pvaLink *pval = *it;
if(pval->debug) {
new_debug = true;
break;
}
}
lchan->debug = new_debug;
}
REFTRACE_DECREMENT(num_instances);
}
static
pvd::StructureConstPtr monitorRequestType = pvd::getFieldCreate()->createFieldBuilder()
->addNestedStructure("field")
->endNested()
->addNestedStructure("record")
->addNestedStructure("_options")
->add("pipeline", pvd::pvBoolean)
->add("atomic", pvd::pvBoolean)
->add("queueSize", pvd::pvUInt)
->endNested()
->endNested()
->createStructure();
pvd::PVStructurePtr pvaLink::makeRequest()
{
pvd::PVStructurePtr ret(pvd::getPVDataCreate()->createPVStructure(monitorRequestType));
ret->getSubFieldT<pvd::PVBoolean>("record._options.pipeline")->put(pipeline);
ret->getSubFieldT<pvd::PVBoolean>("record._options.atomic")->put(true);
ret->getSubFieldT<pvd::PVUInt>("record._options.queueSize")->put(queueSize);
return ret;
}
// caller must lock lchan->lock
bool pvaLink::valid() const
{
return lchan->connected_latched && lchan->op_mon.root;
}
// caller must lock lchan->lock
pvd::PVField::const_shared_pointer pvaLink::getSubField(const char *name)
{
pvd::PVField::const_shared_pointer ret;
if(valid()) {
if(fieldName.empty()) {
// we access the top level struct
ret = lchan->op_mon.root->getSubField(name);
} else {
// we access a sub-struct
ret = lchan->op_mon.root->getSubField(fieldName);
if(!ret) {
// 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
ret.reset();
}
} else {
ret = static_cast<const pvd::PVStructure*>(ret.get())->getSubField(name);
}
}
}
return ret;
}
// call with channel lock held
void pvaLink::onDisconnect()
{
DEBUG(this,<<plink->precord->name<<" disconnect");
// TODO: option to remain queue'd while disconnected
used_queue = used_scratch = false;
}
void pvaLink::onTypeChange()
{
DEBUG(this,<<plink->precord->name<<" type change");
assert(lchan->connected_latched && !!lchan->op_mon.root); // we should only be called when connected
fld_value = getSubField("value");
fld_seconds = std::tr1::dynamic_pointer_cast<const pvd::PVScalar>(getSubField("timeStamp.secondsPastEpoch"));
fld_nanoseconds = std::tr1::dynamic_pointer_cast<const pvd::PVScalar>(getSubField("timeStamp.nanoseconds"));
fld_severity = std::tr1::dynamic_pointer_cast<const pvd::PVScalar>(getSubField("alarm.severity"));
fld_display = std::tr1::dynamic_pointer_cast<const pvd::PVStructure>(getSubField("display"));
fld_control = std::tr1::dynamic_pointer_cast<const pvd::PVStructure>(getSubField("control"));
fld_valueAlarm = std::tr1::dynamic_pointer_cast<const pvd::PVStructure>(getSubField("valueAlarm"));
proc_changed.clear();
// build mask of all "changed" bits associated with our .value
// CP/CPP input links will process this link only for updates where
// the changed mask and proc_changed share at least one set bit.
if(fld_value) {
// bit for this field
proc_changed.set(fld_value->getFieldOffset());
// bits of all parent fields
for(const pvd::PVStructure* parent = fld_value->getParent(); parent; parent = parent->getParent()) {
proc_changed.set(parent->getFieldOffset());
}
if(fld_value->getField()->getType()==pvd::structure)
{
// bits of all child fields
const pvd::PVStructure *val = static_cast<const pvd::PVStructure*>(fld_value.get());
for(size_t i=val->getFieldOffset(), N=val->getNextFieldOffset(); i<N; i++)
proc_changed.set(i);
}
}
}
} // namespace pvalink
+493
View File
@@ -0,0 +1,493 @@
#include <epicsString.h>
#include <alarm.h>
#include <recGbl.h>
#include <epicsStdio.h> // redirect stdout/stderr
#include <pv/current_function.h>
#include "pvalink.h"
namespace {
using namespace pvalink;
#define TRY pvaLink *self = static_cast<pvaLink*>(plink->value.json.jlink); assert(self->alive); try
#define CATCH() catch(std::exception& e) { \
errlogPrintf("pvaLink %s fails %s: %s\n", CURRENT_FUNCTION, plink->precord->name, e.what()); \
}
#define CHECK_VALID() if(!self->valid()) { DEBUG(self, <<CURRENT_FUNCTION<<" "<<self->channelName<<" !valid"); return -1;}
void pvaOpenLink(DBLINK *plink)
{
try {
pvaLink* self((pvaLink*)plink->value.json.jlink);
// workaround for Base not propagating info(base:lsetDebug to us
{
pdbRecordIterator rec(plink->precord);
if(epicsStrCaseCmp(rec.info("base:lsetDebug", "NO"), "YES")==0) {
self->debug = 1;
}
}
DEBUG(self, <<plink->precord->name<<" OPEN "<<self->channelName);
// still single threaded at this point.
// also, no pvaLinkChannel::lock yet
self->plink = plink;
if(self->channelName.empty())
return; // nothing to do...
pvd::PVStructure::const_shared_pointer pvRequest(self->makeRequest());
pvaGlobal_t::channels_key_t key;
{
std::ostringstream strm;
strm<<*pvRequest; // print the request as a convient key for our channel cache
key = std::make_pair(self->channelName, strm.str());
}
std::tr1::shared_ptr<pvaLinkChannel> chan;
bool doOpen = false;
{
Guard G(pvaGlobal->lock);
pvaGlobal_t::channels_t::iterator it(pvaGlobal->channels.find(key));
if(it!=pvaGlobal->channels.end()) {
// re-use existing channel
chan = it->second.lock();
}
if(!chan) {
// open new channel
chan.reset(new pvaLinkChannel(key, pvRequest));
pvaGlobal->channels.insert(std::make_pair(key, chan));
doOpen = true;
}
doOpen &= pvaGlobal->running; // if not running, then open from initHook
}
if(doOpen) {
chan->open(); // start subscription
}
if(!self->local || chan->providerName=="QSRV"){
Guard G(chan->lock);
chan->links.insert(self);
chan->links_changed = true;
self->lchan.swap(chan); // we are now attached
self->lchan->debug |= !!self->debug;
} else {
// TODO: only print duing iocInit()?
fprintf(stderr, "%s Error: local:true link to '%s' can't be fulfilled\n",
plink->precord->name, self->channelName.c_str());
plink->lset = NULL;
}
return;
}CATCH()
// on error, prevent any further calls to our lset functions
plink->lset = NULL;
}
void pvaRemoveLink(struct dbLocker *locker, DBLINK *plink)
{
try {
p2p::auto_ptr<pvaLink> self((pvaLink*)plink->value.json.jlink);
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName);
assert(self->alive);
}CATCH()
}
int pvaIsConnected(const DBLINK *plink)
{
TRY {
Guard G(self->lchan->lock);
bool ret = self->valid();
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<ret);
return ret;
}CATCH()
return 0;
}
int pvaGetDBFtype(const DBLINK *plink)
{
TRY {
Guard G(self->lchan->lock);
CHECK_VALID();
// if fieldName is empty, use top struct value
// if fieldName not empty
// if sub-field is struct, use sub-struct .value
// if sub-field not struct, treat as value
pvd::PVField::const_shared_pointer value(self->getSubField("value"));
pvd::ScalarType ftype = pvd::pvInt; // default for un-mapable types.
if(!value) {
// no-op
} else if(value->getField()->getType()==pvd::scalar)
ftype = static_cast<const pvd::Scalar*>(value->getField().get())->getScalarType();
else if(value->getField()->getType()==pvd::scalarArray)
ftype = static_cast<const pvd::ScalarArray*>(value->getField().get())->getElementType();
int ret;
switch(ftype) {
#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case pvd::pv##PVACODE: ret = DBF_##DBFTYPE;
#define CASE_REAL_INT64
#include "pv/typemap.h"
#undef CASE_REAL_INT64
#undef CASE
case pvd::pvString: ret = DBF_STRING; // TODO: long string?
}
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<dbGetFieldTypeString(ret));
return ret;
}CATCH()
return -1;
}
long pvaGetElements(const DBLINK *plink, long *nelements)
{
TRY {
Guard G(self->lchan->lock);
CHECK_VALID();
long ret = 0;
if(self->fld_value && self->fld_value->getField()->getType()==pvd::scalarArray)
ret = static_cast<const pvd::PVScalarArray*>(self->fld_value.get())->getLength();
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<ret);
return ret;
}CATCH()
return -1;
}
long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer,
long *pnRequest)
{
TRY {
Guard G(self->lchan->lock);
if(!self->valid()) {
// disconnected
if(self->ms != pvaLink::NMS) {
recGblSetSevr(plink->precord, LINK_ALARM, self->snap_severity);
}
// TODO: better capture of disconnect time
epicsTimeGetCurrent(&self->snap_time);
if(self->time) {
plink->precord->time = self->snap_time;
}
DEBUG(self, <<CURRENT_FUNCTION<<" "<<self->channelName<<" !valid");
return -1;
}
if(self->fld_value) {
long status = copyPVD2DBF(self->fld_value, pbuffer, dbrType, pnRequest);
if(status) {
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<status);
return status;
}
}
if(self->fld_seconds) {
self->snap_time.secPastEpoch = self->fld_seconds->getAs<pvd::uint32>() - POSIX_TIME_AT_EPICS_EPOCH;
if(self->fld_nanoseconds) {
self->snap_time.nsec = self->fld_nanoseconds->getAs<pvd::uint32>();
} else {
self->snap_time.nsec = 0u;
}
} else {
self->snap_time.secPastEpoch = 0u;
self->snap_time.nsec = 0u;
}
if(self->fld_severity) {
self->snap_severity = self->fld_severity->getAs<pvd::uint16>();
} else {
self->snap_severity = NO_ALARM;
}
if((self->snap_severity!=NO_ALARM && self->ms == pvaLink::MS) ||
(self->snap_severity==INVALID_ALARM && self->ms == pvaLink::MSI))
{
recGblSetSevr(plink->precord, LINK_ALARM, self->snap_severity);
}
if(self->time) {
plink->precord->time = self->snap_time;
}
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" OK");
return 0;
}CATCH()
return -1;
}
long pvaGetControlLimits(const DBLINK *plink, double *lo, double *hi)
{
TRY {
Guard G(self->lchan->lock);
CHECK_VALID();
if(self->fld_control) {
pvd::PVScalar::const_shared_pointer value;
if(lo) {
value = std::tr1::static_pointer_cast<const pvd::PVScalar>(self->fld_control->getSubField("limitLow"));
*lo = value ? value->getAs<double>() : 0.0;
}
if(hi) {
value = std::tr1::static_pointer_cast<const pvd::PVScalar>(self->fld_control->getSubField("limitHigh"));
*hi = value ? value->getAs<double>() : 0.0;
}
} else {
*lo = *hi = 0.0;
}
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0));
return 0;
}CATCH()
return -1;
}
long pvaGetGraphicLimits(const DBLINK *plink, double *lo, double *hi)
{
TRY {
Guard G(self->lchan->lock);
CHECK_VALID();
if(self->fld_display) {
pvd::PVScalar::const_shared_pointer value;
if(lo) {
value = std::tr1::static_pointer_cast<const pvd::PVScalar>(self->fld_display->getSubField("limitLow"));
*lo = value ? value->getAs<double>() : 0.0;
}
if(hi) {
value = std::tr1::static_pointer_cast<const pvd::PVScalar>(self->fld_display->getSubField("limitHigh"));
*hi = value ? value->getAs<double>() : 0.0;
}
} else {
*lo = *hi = 0.0;
}
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0));
return 0;
}CATCH()
return -1;
}
long pvaGetAlarmLimits(const DBLINK *plink, double *lolo, double *lo,
double *hi, double *hihi)
{
TRY {
//Guard G(self->lchan->lock);
//CHECK_VALID();
*lolo = *lo = *hi = *hihi = 0.0;
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<(lolo ? *lolo : 0)<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0)<<" "<<(hihi ? *hihi : 0));
return 0;
}CATCH()
return -1;
}
long pvaGetPrecision(const DBLINK *plink, short *precision)
{
TRY {
//Guard G(self->lchan->lock);
//CHECK_VALID();
// No sane way to recover precision from display.format string.
*precision = 0;
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<precision);
return 0;
}CATCH()
return -1;
}
long pvaGetUnits(const DBLINK *plink, char *units, int unitsSize)
{
TRY {
Guard G(self->lchan->lock);
CHECK_VALID();
if(unitsSize==0) return 0;
if(units && self->fld_display) {
pvd::PVString::const_shared_pointer value(std::tr1::static_pointer_cast<const pvd::PVString>(self->fld_display->getSubField("units")));
if(value) {
const std::string& egu = value->get();
strncpy(units, egu.c_str(), unitsSize);
}
} else if(units) {
units[0] = '\0';
}
units[unitsSize-1] = '\0';
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<units);
return 0;
}CATCH()
return -1;
}
long pvaGetAlarm(const DBLINK *plink, epicsEnum16 *status,
epicsEnum16 *severity)
{
TRY {
Guard G(self->lchan->lock);
CHECK_VALID();
if(severity) {
*severity = self->snap_severity;
}
if(status) {
*status = self->snap_severity ? LINK_ALARM : NO_ALARM;
}
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<(severity ? *severity : 0)<<" "<<(status ? *status : 0));
return 0;
}CATCH()
return -1;
}
long pvaGetTimeStamp(const DBLINK *plink, epicsTimeStamp *pstamp)
{
TRY {
Guard G(self->lchan->lock);
CHECK_VALID();
if(pstamp) {
*pstamp = self->snap_time;
}
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<(pstamp ? pstamp->secPastEpoch : 0)<<":"<<(pstamp ? pstamp->nsec: 0));
return 0;
}CATCH()
return -1;
}
// note that we handle DBF_ENUM differently than in pvif.cpp
pvd::ScalarType DBR2PVD(short dbr)
{
switch(dbr) {
#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case DBR_##DBFTYPE: return pvd::pv##PVACODE;
#define CASE_SKIP_BOOL
#define CASE_REAL_INT64
#include "pv/typemap.h"
#undef CASE_SKIP_BOOL
#undef CASE_REAL_INT64
#undef CASE
case DBF_ENUM: return pvd::pvUShort;
case DBF_STRING: return pvd::pvString;
}
throw std::invalid_argument("Unsupported DBR code");
}
long pvaPutValue(DBLINK *plink, short dbrType,
const void *pbuffer, long nRequest)
{
TRY {
(void)self;
Guard G(self->lchan->lock);
if(nRequest < 0) return -1;
if(!self->retry && !self->valid()) {
DEBUG(self, <<CURRENT_FUNCTION<<" "<<self->channelName<<" !valid");
return -1;
}
pvd::ScalarType stype = DBR2PVD(dbrType);
pvd::shared_vector<const void> buf;
if(dbrType == DBF_STRING) {
const char *sbuffer = (const char*)pbuffer;
pvd::shared_vector<std::string> sval(nRequest);
for(long n=0; n<nRequest; n++, sbuffer += MAX_STRING_SIZE) {
sval[n] = std::string(sbuffer, epicsStrnLen(sbuffer, MAX_STRING_SIZE));
}
self->put_scratch = pvd::static_shared_vector_cast<const void>(pvd::freeze(sval));
} else {
pvd::shared_vector<void> val(pvd::ScalarTypeFunc::allocArray(stype, size_t(nRequest)));
assert(size_t(dbValueSize(dbrType)*nRequest) == val.size());
memcpy(val.data(), pbuffer, val.size());
self->put_scratch = pvd::freeze(val);
}
self->used_scratch = true;
if(!self->defer) self->lchan->put();
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<self->lchan->op_put.valid());
return 0;
}CATCH()
return -1;
}
void pvaScanForward(DBLINK *plink)
{
TRY {
Guard G(self->lchan->lock);
if(!self->retry && !self->valid()) {
return;
}
// FWD_LINK is never deferred, and always results in a Put
self->lchan->put(true);
DEBUG(self, <<plink->precord->name<<" "<<CURRENT_FUNCTION<<" "<<self->channelName<<" "<<self->lchan->op_put.valid());
}CATCH()
}
#undef TRY
#undef CATCH
} //namespace
namespace pvalink {
lset pva_lset = {
0, 1, // non-const, volatile
&pvaOpenLink,
&pvaRemoveLink,
NULL, NULL, NULL,
&pvaIsConnected,
&pvaGetDBFtype,
&pvaGetElements,
&pvaGetValue,
&pvaGetControlLimits,
&pvaGetGraphicLimits,
&pvaGetAlarmLimits,
&pvaGetPrecision,
&pvaGetUnits,
&pvaGetAlarm,
&pvaGetTimeStamp,
&pvaPutValue,
NULL,
&pvaScanForward
//&pvaReportLink,
};
} //namespace pvalink
+16
View File
@@ -0,0 +1,16 @@
#include <epicsExport.h>
static void installPVAAddLinkHook() {}
struct jlif {} lsetPVA;
extern "C" {
int pvaLinkDebug;
int pvaLinkNWorkers;
epicsExportRegistrar(installPVAAddLinkHook);
epicsExportAddress(jlif, lsetPVA);
epicsExportAddress(int, pvaLinkDebug);
epicsExportAddress(int, pvaLinkNWorkers);
}
+300 -135
View File
@@ -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,12 +704,24 @@ struct PVIFScalarNumeric : public PVIF
}
}
virtual pvd::Status get(const epics::pvData::BitSet& mask, proc_t proc) OVERRIDE FINAL
virtual pvd::Status get(const epics::pvData::BitSet& mask, proc_t proc, bool permit) OVERRIDE FINAL
{
pvd::Status ret;
if(mask.logical_and(pvmeta.maskVALUEPut)) {
getValue(pvmeta.chan, pvmeta.value.get());
ret = PVIF::get(mask, proc);
pvd::Status ret = checkDISP(chan);
if(!ret)
return ret;
bool newval = mask.logical_and(pvmeta.maskVALUEPut);
if(newval) {
if(permit)
getValue(pvmeta.chan, pvmeta.value.get());
else
ret = pvd::Status::error("Put not permitted");
}
if(newval || proc==PVIF::ProcForce) {
if(permit)
ret = PVIF::get(mask, proc);
else
ret = pvd::Status::error("Process not permitted");
}
return ret;
}
@@ -628,6 +741,7 @@ struct PVIFScalarNumeric : public PVIF
} // namespace
static
pvd::ScalarType DBR2PVD(short dbr)
{
switch(dbr) {
@@ -639,26 +753,29 @@ pvd::ScalarType DBR2PVD(short dbr)
#undef CASE_SKIP_BOOL
#undef CASE
case DBF_STRING: return pvd::pvString;
default:
throw std::invalid_argument("Unsupported DBR code");
}
throw std::invalid_argument("Unsupported DBR code");
}
short PVD2DBR(pvd::ScalarType pvt)
{
switch(pvt) {
#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case pvd::pv##PVACODE: return DBR_##DBFTYPE;
#define CASE_SQUEEZE_INT64
#ifndef USE_INT64
# define CASE_SQUEEZE_INT64
#endif
#include "pv/typemap.h"
#undef CASE_SQUEEZE_INT64
#ifndef USE_INT64
# undef CASE_SQUEEZE_INT64
#endif
#undef CASE
default:
throw std::invalid_argument("Unsupported pvType code");
case pvd::pvString: return DBF_STRING;
}
return -1;
}
epics::pvData::FieldConstPtr
ScalarBuilder::dtype(dbChannel *channel)
ScalarBuilder::dtype()
{
short dbr = dbChannelFinalFieldType(channel);
const long maxelem = dbChannelFinalElements(channel);
@@ -670,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:");
@@ -695,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) {
@@ -705,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:
@@ -725,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);
}
@@ -756,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);
@@ -764,17 +915,29 @@ struct PVIFPlain : public PVIF
}
}
virtual pvd::Status get(const epics::pvData::BitSet& mask, proc_t proc)
virtual pvd::Status get(const epics::pvData::BitSet& mask, proc_t proc, bool permit) OVERRIDE FINAL
{
pvd::Status ret;
if(mask.get(fieldOffset)) {
getValue(channel, field.get());
ret = PVIF::get(mask, proc);
pvd::Status ret = checkDISP(chan);
if(!ret)
return ret;
bool newval = mask.get(fieldOffset);
if(newval) {
if(permit)
getValue(channel, field.get());
else
ret = pvd::Status::error("Put not permitted");
}
if(newval || proc==PVIF::ProcForce) {
if(permit)
ret = PVIF::get(mask, proc);
else
ret = pvd::Status::error("Process not permitted");
}
return ret;
}
virtual unsigned dbe(const epics::pvData::BitSet& mask)
virtual unsigned dbe(const epics::pvData::BitSet& mask) OVERRIDE FINAL
{
// TODO: figure out how to handle various intermidiate compressed
// bitSet and enclosing.
@@ -787,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);
@@ -807,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)
@@ -827,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();
}
@@ -838,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)
@@ -884,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());
@@ -896,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)
@@ -905,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;
@@ -921,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()) {
@@ -947,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)
@@ -966,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;
}
@@ -985,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)
@@ -1010,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);
@@ -1022,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);
@@ -1046,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);
}
+84 -20
View File
@@ -3,6 +3,7 @@
#include <map>
#include <asLib.h>
#include <dbAccess.h>
#include <dbChannel.h>
#include <dbStaticLib.h>
@@ -15,7 +16,7 @@
#include <pv/pvData.h>
#include <pv/anyscalar.h>
#include <shareLib.h>
#include <pv/qsrv.h>
#ifndef VERSION_INT
# define VERSION_INT(V,R,M,P) ( ((V)<<24) | ((R)<<16) | ((M)<<8) | (P))
@@ -29,9 +30,25 @@
# define USE_MULTILOCK
#endif
epics::pvData::ScalarType DBR2PVD(short dbr);
namespace epics {
namespace pvAccess {
class ChannelRequester;
}
}
short PVD2DBR(epics::pvData::ScalarType pvt);
// copy from PVField (.value sub-field) to DBF buffer
QSRV_API
long copyPVD2DBF(const epics::pvData::PVField::const_shared_pointer& in,
void *outbuf, short outdbf, long *outnReq);
// copy from DBF buffer to PVField (.value sub-field)
QSRV_API
long copyDBF2PVD(const epics::pvData::shared_vector<const void>& buf,
const epics::pvData::PVField::shared_pointer& out,
epics::pvData::BitSet &changed,
const epics::pvData::PVStringArray::const_svector& choices);
union dbrbuf {
epicsInt8 dbf_CHAR;
epicsUInt8 dbf_UCHAR;
@@ -42,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()
@@ -64,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)
@@ -109,6 +150,13 @@ struct pdbRecordIterator {
#endif
m_done = false;
}
#if EPICS_VERSION_INT>=VERSION_INT(3,16,1,0)
pdbRecordIterator(dbCommon *prec)
{
dbInitEntryFromRecord(prec, &ent);
m_done = false;
}
#endif
~pdbRecordIterator()
{
dbFinishEntry(&ent);
@@ -132,7 +180,7 @@ struct pdbRecordIterator {
return m_done ? NULL : (dbCommon*)ent.precnode->precord;
}
const char *name() const {
return m_done ? NULL : ((dbCommon*)ent.precnode->precord)->name;
return m_done ? NULL : ent.precnode->recordname;
}
const char *info(const char *key, const char *def =0)
{
@@ -206,6 +254,7 @@ struct LocalFL
if(pfl) pfl = dbChannelRunPostChain(pchan, pfl);
}
}
this->pfl = pfl;
}
~LocalFL() {
if(ours) db_delete_field_log(pfl);
@@ -229,8 +278,8 @@ struct DBManyLock
{
dbLocker *plock;
DBManyLock() :plock(NULL) {}
DBManyLock(const std::vector<dbCommon*>& recs, unsigned flags)
:plock(dbLockerAlloc((dbCommon**)&recs[0], recs.size(), flags))
DBManyLock(const std::vector<dbCommon*>& recs, unsigned flags=0)
:plock(dbLockerAlloc( (recs.size() > 0 ? (dbCommon**)&recs[0] : NULL), recs.size(), flags))
{
if(!plock) throw std::invalid_argument("Failed to create locker");
}
@@ -261,7 +310,7 @@ struct DBManyLocker
};
#endif
struct epicsShareClass FieldName
struct QSRV_API FieldName
{
struct Component {
std::string name;
@@ -304,7 +353,7 @@ private:
FieldName& operator=(const FieldName&);
};
struct epicsShareClass PVIF {
struct QSRV_API PVIF {
PVIF(dbChannel *ch);
virtual ~PVIF() {}
@@ -319,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;
@@ -330,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;
};
+17
View File
@@ -0,0 +1,17 @@
# Changes to this file may require incrementing
# the ABI version in CONFIG_QSRV_VERSION
registrar(QSRVRegistrar)
registrar(installPVAAddLinkHook)
link("pva", "lsetPVA")
# from demo.cpp
device(waveform, CONSTANT, devWfPDBDemo, "QSRV Demo")
# from imagedemo.c
function(QSRV_image_demo)
# from pdb.cpp
# Extra debug info when parsing group definitions
variable(PDBProviderDebug, int)
# Number of worker threads for handling monitor updates.
# Default: 1
variable(pvaLinkNWorkers, int)
+4 -5
View File
@@ -2,16 +2,15 @@
# the ABI version in CONFIG_QSRV_VERSION
registrar(QSRVRegistrar)
#registrar(installPVAAddLinkHook)
#link("pva", "lsetPVA")
registrar(installPVAAddLinkHook)
# from demo.cpp
device(waveform, CONSTANT, devWfPDBDemo, "QSRV Demo")
# from imagedemo.c
function(QSRV_image_demo)
# from pvif.cpp
# Disable mapping of display.format
variable(qsrvDisableFormat, int)
# from pdb.cpp
# Extra debug info when parsing group definitions
variable(PDBProviderDebug, int)
# Number of worker threads for handling monitor updates.
# Default: 1
variable(pvaLinkNWorkers, int)
+53 -2
View File
@@ -18,8 +18,7 @@
#include <pv/serverContext.h>
#include <pv/iocshelper.h>
#define epicsExportSharedSymbols
#include "pv/qsrv.h"
#include "pvahelper.h"
#include "pvif.h"
#include "pdb.h"
@@ -47,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)
@@ -89,10 +129,21 @@ 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
unsigned qsrvVersion(void)
{
return QSRV_VERSION_INT;
}
unsigned qsrvABIVersion(void)
{
return QSRV_ABI_VERSION_INT;
}
extern "C" {
epicsExportRegistrar(QSRVRegistrar);
}
+241 -207
View File
@@ -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;
}
+135
View File
@@ -0,0 +1,135 @@
#include <typeinfo>
#include <stdexcept>
#include <epicsEvent.h>
#include <epicsGuard.h>
#include <epicsThread.h>
#include <errlog.h>
#include <pv/sharedPtr.h>
#include "helper.h"
#include "tpool.h"
typedef epicsGuard<epicsMutex> Guard;
typedef epicsGuardRelease<epicsMutex> UnGuard;
WorkQueue::WorkQueue(const std::string& name)
:name(name)
,state(Idle)
{}
WorkQueue::~WorkQueue() { close(); }
void WorkQueue::start(unsigned nworkers, unsigned prio)
{
Guard G(mutex);
if(state!=Idle)
throw std::logic_error("Already started");
try {
state = Active;
for(unsigned i=0; i<nworkers; i++) {
p2p::auto_ptr<epicsThread> worker(new epicsThread(*this, name.c_str(),
epicsThreadGetStackSize(epicsThreadStackSmall),
prio));
worker->start();
workers.push_back(worker.get());
worker.release();
}
}catch(...){
UnGuard U(G); // unlock as close() blocks to join any workers which were started
close();
throw;
}
}
void WorkQueue::close()
{
workers_t temp;
{
Guard G(mutex);
if(state!=Active)
return;
temp.swap(workers);
state = Stopping;
}
wakeup.signal();
for(workers_t::iterator it(temp.begin()), end(temp.end()); it!=end; ++it)
{
(*it)->exitWait();
delete *it;
}
{
Guard G(mutex);
state = Idle;
}
}
void WorkQueue::add(const value_type& work)
{
bool empty;
{
Guard G(mutex);
if(state!=Active)
return;
empty = queue.empty();
queue.push_back(work);
}
if(empty) {
wakeup.signal();
}
}
void WorkQueue::run()
{
Guard G(mutex);
std::tr1::shared_ptr<epicsThreadRunable> work;
while(state==Active) {
if(!queue.empty()) {
work = queue.front().lock();
queue.pop_front();
}
bool last = queue.empty();
{
UnGuard U(G);
if(work) {
try {
work->run();
work.reset();
}catch(std::exception& e){
errlogPrintf("%s Unhandled exception from %s: %s\n",
name.c_str(), typeid(work.get()).name(), e.what());
work.reset();
}
}
if(last) {
wakeup.wait();
}
}
}
// pass along the close() signal to next worker
wakeup.signal();
}
+52
View File
@@ -0,0 +1,52 @@
#ifndef TPOOL_H
#define TPOOL_H
#include <stdexcept>
#include <deque>
#include <vector>
#include <string>
#include <errlog.h>
#include <epicsThread.h>
#include <epicsMutex.h>
#include <epicsEvent.h>
#include <pv/sharedPtr.h>
struct WorkQueue : private epicsThreadRunable
{
typedef std::tr1::weak_ptr<epicsThreadRunable> value_type;
private:
const std::string name;
epicsMutex mutex;
enum state_t {
Idle,
Active,
Stopping,
} state;
typedef std::deque<value_type> queue_t;
queue_t queue;
epicsEvent wakeup;
typedef std::vector<epicsThread*> workers_t;
workers_t workers;
public:
WorkQueue(const std::string& name);
virtual ~WorkQueue();
void start(unsigned nworkers=1, unsigned prio = epicsThreadPriorityLow);
void close();
void add(const value_type& work);
private:
virtual void run();
};
#endif // TPOOL_H
+18 -7
View File
@@ -14,6 +14,10 @@ p2pTestIoc_DBD += base.dbd
# Tests explicitly create/destory PDB provider
#p2pTestIoc_DBD += qsrv.dbd
TARGETS += $(COMMON_DIR)/pvaLinkTestIoc.dbd
pvaLinkTestIoc_DBD += base.dbd
pvaLinkTestIoc_DBD += qsrv.dbd
PROD_SRCS += utilitiesx.cpp
PROD_LIBS += pvAccess pvData
PROD_LIBS += $(EPICS_BASE_IOC_LIBS)
@@ -40,20 +44,27 @@ testpdb_SRCS += p2pTestIoc_registerRecordDeviceDriver.cpp
testpdb_LIBS += qsrv
TESTS += testpdb
PROD_HOST += check_consist
TESTPROD_HOST += check_consist
check_consist_SRCS += check_consist.cpp
#TESTPROD_HOST += testpvalink
testpvalink_SRCS += testpvalink.cpp
testpvalink_SRCS += p2pTestIoc_registerRecordDeviceDriver.cpp
testpvalink_LIBS += qsrv
#TESTS += testpvalink
ifdef BASE_3_16
TESTPROD_HOST += testpvalink
testpvalink_SRCS += testpvalink.cpp
testpvalink_SRCS += pvaLinkTestIoc_registerRecordDeviceDriver.cpp
testpvalink_LIBS += qsrv
TESTS += testpvalink
TESTPROD_HOST += testgroupconfig
testgroupconfig_SRCS += testgroupconfig
testgroupconfig_LIBS += qsrv pvAccess pvData
testgroupconfig_LIBS += $(EPICS_BASE_IOC_LIBS)
TESTS += testgroupconfig
TESTPROD_HOST += testdbf_copy
testdbf_copy_SRCS += testdbf_copy
testdbf_copy_LIBS += qsrv pvAccess pvData
testdbf_copy_LIBS += $(EPICS_BASE_IOC_LIBS)
TESTS += testdbf_copy
endif
TESTSCRIPTS_HOST += $(TESTS:%=%.t)
+271
View File
@@ -0,0 +1,271 @@
#include <stdexcept>
#include <pv/pvUnitTest.h>
#include <testMain.h>
#include <dbStaticLib.h>
#include <epicsTypes.h>
#include <pv/valueBuilder.h>
#include <pv/pvData.h>
#include "pvif.h"
namespace pvd = epics::pvData;
namespace {
template<pvd::ScalarType ENUM, typename DBF, typename E>
void testPVD2DBR_scalar(unsigned dbf, typename pvd::meta::arg_type<typename pvd::ScalarTypeTraits<ENUM>::type>::type V, E expect)
{
testDiag("testPVD2DBR_scalar(%s, %s)", pvd::ScalarTypeFunc::name(ENUM), dbGetFieldTypeString(dbf));
pvd::PVStructure::shared_pointer top(pvd::ValueBuilder()
.add<ENUM>("value", V)
.buildPVStructure());
DBF buf;
copyPVD2DBF(top->getSubFieldT("value"), &buf, dbf, NULL);
testEqual(buf, expect);
}
void testPVD2DBR_enum()
{
testDiag("testPVD2DBR_enum()");
pvd::shared_vector<std::string> choices(3);
choices[0] = "zero";
choices[1] = "one";
choices[2] = "two";
pvd::PVStructure::shared_pointer top(pvd::ValueBuilder()
.addNested("value")
.add<pvd::pvInt>("index", 1)
.add("choices", pvd::static_shared_vector_cast<const void>(pvd::freeze(choices)))
.endNested()
.buildPVStructure());
{
epicsEnum16 ival;
copyPVD2DBF(top->getSubFieldT("value"), &ival, DBF_ENUM, NULL);
testEqual(ival, 1);
ival = 0;
testOk1(!!top->getSubField("value"));
copyPVD2DBF(top->getSubFieldT("value"), &ival, DBF_USHORT, NULL);
testEqual(ival, 1);
}
{
epicsUInt32 ival;
copyPVD2DBF(top->getSubFieldT("value"), &ival, DBF_LONG, NULL);
testEqual(ival, 1u);
}
char sval[MAX_STRING_SIZE];
copyPVD2DBF(top->getSubFieldT("value"), sval, DBF_STRING, NULL);
testEqual(std::string(sval) , "one");
}
void testPVD2DBR_array()
{
testDiag("testPVD2DBR_array()");
pvd::shared_vector<const pvd::uint32> arr;
{
pvd::shared_vector<pvd::uint32> iarr(3);
iarr[0] = 1; iarr[1] = 2; iarr[2] = 3;
arr = pvd::freeze(iarr);
}
pvd::PVStructure::shared_pointer top(pvd::ValueBuilder()
.add("value", pvd::static_shared_vector_cast<const void>(arr))
.buildPVStructure());
{
epicsUInt16 sarr[5];
{
long nreq = 5;
copyPVD2DBF(top->getSubFieldT("value"), sarr, DBF_SHORT, &nreq);
testEqual(nreq, 3);
}
testEqual(sarr[0], arr[0]);
testEqual(sarr[1], arr[1]);
testEqual(sarr[2], arr[2]);
}
{
char sarr[MAX_STRING_SIZE*5];
{
long nreq = 5;
copyPVD2DBF(top->getSubFieldT("value"), sarr, DBF_STRING, &nreq);
testEqual(nreq, 3);
}
testEqual(sarr[0*MAX_STRING_SIZE+0], '1');
testEqual(int(sarr[0*MAX_STRING_SIZE+1]), int('\0'));
testEqual(sarr[1*MAX_STRING_SIZE+0], '2');
testEqual(int(sarr[1*MAX_STRING_SIZE+1]), int('\0'));
testEqual(sarr[2*MAX_STRING_SIZE+0], '3');
testEqual(int(sarr[2*MAX_STRING_SIZE+1]), int('\0'));
}
}
template<typename input_t, typename output_t>
void testDBR2PVD_scalar(const input_t& input,
const output_t& expect)
{
pvd::ScalarType in = (pvd::ScalarType)pvd::ScalarTypeID<input_t>::value;
pvd::ScalarType out = (pvd::ScalarType)pvd::ScalarTypeID<output_t>::value;
testDiag("testDBR2PVD_scalar(%s, %s)", pvd::ScalarTypeFunc::name(in), pvd::ScalarTypeFunc::name(out));
pvd::PVStructure::shared_pointer top(pvd::getPVDataCreate()->createPVStructure(pvd::getFieldCreate()->createFieldBuilder()
->add("value", out) // initially zero or ""
->createStructure()));
pvd::PVStringArray::const_svector choices;
pvd::BitSet changed;
pvd::shared_vector<input_t> buf(1);
buf[0] = input;
copyDBF2PVD(pvd::static_shared_vector_cast<const void>(pvd::freeze(buf)),
top->getSubFieldT("value"), changed, choices);
output_t actual = top->getSubFieldT<pvd::PVScalar>("value")->getAs<output_t>();
testEqual(actual, expect);
testOk1(changed.get(top->getSubFieldT("value")->getFieldOffset()));
}
template<typename input_t>
void testDBR2PVD_enum(const input_t& input, pvd::int32 expect)
{
testDiag("testDBR2PVD_enum()");
pvd::shared_vector<const std::string> choices;
{
pvd::shared_vector<std::string> temp(3);
temp[0] = "zero";
temp[1] = "one";
temp[2] = "two";
choices = pvd::freeze(temp);
}
pvd::PVStructure::shared_pointer top(pvd::ValueBuilder()
.addNested("value")
.add<pvd::pvInt>("index", 0)
.add("choices", pvd::static_shared_vector_cast<const void>(choices))
.endNested()
.buildPVStructure());
pvd::BitSet changed;
pvd::shared_vector<input_t> buf(1);
buf[0] = input;
copyDBF2PVD(pvd::static_shared_vector_cast<const void>(pvd::freeze(buf)),
top->getSubFieldT("value"), changed, choices);
pvd::int32 actual = top->getSubFieldT<pvd::PVScalar>("value.index")->getAs<pvd::int32>();
testShow()<<top;
testShow()<<changed;
testEqual(actual, expect);
testOk1(changed.get(top->getSubFieldT("value.index")->getFieldOffset()));
}
void testDBR2PVD_array()
{
testDiag("testDBR2PVD_array()");
pvd::PVStructure::shared_pointer top(pvd::getPVDataCreate()->createPVStructure(pvd::getFieldCreate()->createFieldBuilder()
->addArray("value", pvd::pvInt) // initially zero or ""
->createStructure()));
pvd::PVStringArray::const_svector choices;
pvd::BitSet changed;
{
pvd::shared_vector<pvd::uint32> buf(3);
buf[0] = 1; buf[1] = 2; buf[2] = 3;
copyDBF2PVD(pvd::static_shared_vector_cast<const void>(pvd::freeze(buf)),
top->getSubFieldT("value"), changed, choices);
pvd::PVIntArray::const_svector arr(top->getSubFieldT<pvd::PVIntArray>("value")->view());
testEqual(arr.size(), 3u);
testEqual(arr[0], 1);
testEqual(arr[1], 2);
testEqual(arr[2], 3);
testOk1(changed.get(top->getSubFieldT("value")->getFieldOffset()));
}
changed.clear();
{
pvd::shared_vector<std::string> buf(4);
buf[0] = "4";
buf[1] = "5";
buf[2] = "6";
buf[3] = "7";
copyDBF2PVD(pvd::static_shared_vector_cast<const void>(pvd::freeze(buf)),
top->getSubFieldT("value"), changed, choices);
pvd::PVIntArray::const_svector arr(top->getSubFieldT<pvd::PVIntArray>("value")->view());
testEqual(arr.size(), 4u);
testEqual(arr[0], 4);
testEqual(arr[1], 5);
testEqual(arr[2], 6);
testEqual(arr[3], 7);
testOk1(changed.get(top->getSubFieldT("value")->getFieldOffset()));
}
}
}
MAIN(testdbf_copy)
{
testPlan(53);
try{
testPVD2DBR_scalar<pvd::pvDouble, double>(DBF_DOUBLE, 42.2, 42.2);
testPVD2DBR_scalar<pvd::pvDouble, pvd::uint16>(DBF_USHORT, 42.2, 42u);
testPVD2DBR_scalar<pvd::pvInt, pvd::int32>(DBF_LONG, 42, 42);
testPVD2DBR_scalar<pvd::pvInt, char[MAX_STRING_SIZE]>(DBF_STRING, 42, std::string("42"));
testPVD2DBR_scalar<pvd::pvLong, pvd::int64>(DBF_INT64, 42, 42);
testPVD2DBR_scalar<pvd::pvLong, char[MAX_STRING_SIZE]>(DBF_STRING, 42, std::string("42"));
testPVD2DBR_scalar<pvd::pvUShort, pvd::uint16>(DBF_USHORT, 41u, 41);
testPVD2DBR_scalar<pvd::pvByte, pvd::int8>(DBF_CHAR, 41, 41);
testPVD2DBR_scalar<pvd::pvString, char[MAX_STRING_SIZE]>(DBF_STRING, "hello", std::string("hello"));
testPVD2DBR_scalar<pvd::pvString, pvd::int32>(DBF_LONG, "-100", -100);
//testPVD2DBR_scalar<pvd::pvBoolean, pvd::int8>(DBF_CHAR, true, 1);
testPVD2DBR_enum();
testPVD2DBR_array();
testDBR2PVD_scalar<double, double>(42.2, 42.2);
testDBR2PVD_scalar<pvd::uint16, double>(42u, 42.0);
testDBR2PVD_scalar<pvd::int32, pvd::int32>(-41, -41);
testDBR2PVD_scalar<std::string, pvd::int32>("-41", -41);
testDBR2PVD_scalar<std::string, std::string>("hello", "hello");
testDBR2PVD_scalar<pvd::int32, std::string>(-42, "-42");
testDBR2PVD_enum<pvd::uint32>(2, 2);
testDBR2PVD_enum<std::string>("two", 2);
testDBR2PVD_array();
}catch(std::exception& e){
testFail("Unexpected exception: %s", e.what());
}
return testDone();
}
+4
View File
@@ -0,0 +1,4 @@
record (waveform, "TEST") {
field(FTVL, "SHORT")
field(NELM, "10")
}
+6 -5
View File
@@ -18,6 +18,7 @@ void test_parse()
" \"+atomic\":false,\n"
" \"fld\":{\n"
" \"+type\": \"simple\","
" \"+channel\": \"VAL\","
" \"+putorder\": -4"
" },\n"
" \"\":{\n"
@@ -29,7 +30,7 @@ void test_parse()
"}";
GroupConfig conf;
GroupConfig::parse(txt, conf);
GroupConfig::parse(txt, "rec", conf);
testOk(conf.warning.empty(), "Warnings: %s", conf.warning.c_str());
@@ -37,7 +38,7 @@ void test_parse()
testOk1(conf.groups["grpa"].atomic_set);
testEqual(conf.groups["grpa"].fields["fld"].type, "simple");
testEqual(conf.groups["grpa"].fields["fld"].channel, "");
testEqual(conf.groups["grpa"].fields["fld"].channel, "rec.VAL");
testEqual(conf.groups["grpa"].fields["fld"].putorder, -4);
testEqual(conf.groups["grpa"].fields[""].type, "top");
@@ -49,15 +50,15 @@ void test_fail()
{
GroupConfig conf;
testThrows(std::runtime_error, GroupConfig::parse("{", conf));
testThrows(std::runtime_error, GroupConfig::parse("{", "", conf));
}
{
GroupConfig conf;
testThrows(std::runtime_error, GroupConfig::parse("{\"G\":{\"F\":{\"K\":{}}}}", conf));
testThrows(std::runtime_error, GroupConfig::parse("{\"G\":{\"F\":{\"K\":{}}}}", "", conf));
}
{
GroupConfig conf;
testThrows(std::runtime_error, GroupConfig::parse("{\"G\":5}", conf));
testThrows(std::runtime_error, GroupConfig::parse("{\"G\":5}", "", conf));
}
}
+42 -1
View File
@@ -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);
}
+41 -26
View File
@@ -1,9 +1,13 @@
#include <dbUnitTest.h>
#include <testMain.h>
#include <int64inRecord.h>
#include <int64outRecord.h>
#include <pv/qsrv.h>
#include "utilities.h"
#include "pvalink.h"
#include "pv/qsrv.h"
namespace {
@@ -11,66 +15,77 @@ void testGet()
{
testDiag("==== testGet ====");
testdbGetFieldEqual("target:li.VAL", DBF_LONG, 42);
testdbGetFieldEqual("src:li1.VAL", DBF_LONG, 0);
testdbGetFieldEqual("src:li1.INP", DBF_STRING, "{\"pva\":\"target:li\"}");
int64inRecord *i1 = (int64inRecord*)testdbRecordPtr("src:i1");
testdbPutFieldOk("src:li1.PROC", DBF_LONG, 1);
//TODO: wait for dbEvent queue update
epicsThreadSleep(0.1);
while(!dbIsLinkConnected(&i1->inp))
testqsrvWaitForLinkEvent(&i1->inp);
testdbGetFieldEqual("src:li1.VAL", DBF_LONG, 42);
testdbGetFieldEqual("target:i.VAL", DBF_INT64, 42LL);
testdbPutFieldOk("src:li1.INP", DBF_STRING, "{\"pva\":\"target:ai\"}");
testdbGetFieldEqual("src:i1.VAL", DBF_INT64, 0LL); // value before first process
testdbGetFieldEqual("src:li1.VAL", DBF_LONG, 42);
testdbGetFieldEqual("src:i1.INP", DBF_STRING, "{\"pva\":\"target:i\"}");
//TODO: wait for pvalink worker update
epicsThreadSleep(0.1);
testdbPutFieldOk("src:li1.PROC", DBF_LONG, 1);
//TODO: wait for dbEvent queue update
epicsThreadSleep(0.1);
testdbPutFieldOk("src:i1.PROC", DBF_INT64, 1LL);
testdbGetFieldEqual("src:li1.VAL", DBF_LONG, 4);
testdbGetFieldEqual("src:i1.VAL", DBF_INT64, 42LL);
testdbPutFieldOk("src:i1.INP", DBF_STRING, "{\"pva\":\"target:ai\"}");
while(!dbIsLinkConnected(&i1->inp))
testqsrvWaitForLinkEvent(&i1->inp);
testdbGetFieldEqual("src:i1.VAL", DBF_INT64, 42LL); // changing link doesn't automatically process
testdbPutFieldOk("src:i1.PROC", DBF_INT64, 1LL);
testdbGetFieldEqual("src:i1.VAL", DBF_INT64, 4LL); // now it's changed
}
void testPut()
{
testDiag("==== testPut ====");
testdbGetFieldEqual("target:li2.VAL", DBF_LONG, 43);
testdbGetFieldEqual("src:li2.VAL", DBF_LONG, 0);
testdbGetFieldEqual("src:li2.INP", DBF_STRING, "{\"pva\":\"target:l2\"}");
testdbPutFieldOk("src:li2.VAL", DBF_LONG, 14);
int64outRecord *o2 = (int64outRecord*)testdbRecordPtr("src:o2");
testdbGetFieldEqual("target:li2.VAL", DBF_LONG, 14);
testdbGetFieldEqual("src:li2.VAL", DBF_LONG, 14);
while(!dbIsLinkConnected(&o2->out))
testqsrvWaitForLinkEvent(&o2->out);
testdbGetFieldEqual("target:i2.VAL", DBF_INT64, 43LL);
testdbGetFieldEqual("src:o2.VAL", DBF_INT64, 0LL);
testdbGetFieldEqual("src:o2.OUT", DBF_STRING, "{\"pva\":\"target:i2\"}");
testdbPutFieldOk("src:o2.VAL", DBF_INT64, 14LL);
testdbGetFieldEqual("target:i2.VAL", DBF_INT64, 14LL);
testdbGetFieldEqual("src:o2.VAL", DBF_INT64, 14LL);
}
} // namespace
extern "C"
void p2pTestIoc_registerRecordDeviceDriver(struct dbBase *);
void pvaLinkTestIoc_registerRecordDeviceDriver(struct dbBase *);
MAIN(testpvalink)
{
testPlan(0);
testPlan(15);
// Disable PVA client provider, use local/QSRV provider
pvaLinkIsolate = 1;
pvaLinkDebug = 5;
try {
TestIOC IOC;
testdbReadDatabase("p2pTestIoc.dbd", NULL, NULL);
p2pTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("pvaLinkTestIoc.dbd", NULL, NULL);
pvaLinkTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("testpvalink.db", NULL, NULL);
IOC.init();
testGet();
testPut();
testqsrvShutdownOk();
IOC.shutdown();
testqsrvCleanup();
}catch(std::exception& e){
testFail("Unexpected exception: %s", e.what());
+6 -6
View File
@@ -1,21 +1,21 @@
# used by testGet()
record(longin, "target:li") {
record(int64in, "target:i") {
field(VAL, "42")
}
record(ai, "target:ai") {
field(VAL, "4.0")
}
record(longin, "src:li1") {
field(INP, {pva:"target:li"})
record(int64in, "src:i1") {
field(INP, {"pva":"target:i"})
}
# used by testPut()
record(longin, "target:li2") {
record(int64in, "target:i2") {
field(VAL, "43")
}
record(longout, "src:li2") {
field(OUT, {pva:"target:li"})
record(int64out, "src:o2") {
field(OUT, {"pva":"target:i2"})
}
+260 -54
View File
@@ -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());
@@ -289,8 +418,6 @@ void testScalar()
pvif_mbbi->get(mask);
testEqual(prec_mbbi->val, 2);
dbScanUnlock((dbCommon*)prec_mbbi);
iocshCmd("stopPVAServer");
}
void testPlain()
@@ -315,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();
@@ -412,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();
}
+1
View File
@@ -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")
+8
View File
@@ -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")
}