Compare commits
127 Commits
R7.0.7
...
dirks-last
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42604fc794 | ||
|
|
4ecc0daa79 | ||
|
|
5a1f3ecc8b | ||
|
|
cb97d662a7 | ||
|
|
d4fab0d20e | ||
|
|
0c13e6ba6c | ||
|
|
8f1243da40 | ||
|
|
fe9995c0b5 | ||
|
|
f56412d6a5 | ||
|
|
07d18c55ba | ||
|
|
9f97f25669 | ||
| 912a82c0b5 | |||
| acf2241fd0 | |||
|
|
b878295d06 | ||
| f41f11c7f6 | |||
|
|
216359974c | ||
|
|
17ad04505e | ||
|
|
3500a02034 | ||
|
|
52b18d56a0 | ||
|
|
5507646ce7 | ||
|
|
625c2ef159 | ||
|
|
bdaca51d96 | ||
|
|
9655b78e11 | ||
|
|
e11f88017d | ||
|
|
8a3020033e | ||
|
|
bd1af9ac95 | ||
|
|
d5959ca20a | ||
|
|
e10dcede7d | ||
|
|
c042b08ab0 | ||
|
|
5eff3803a8 | ||
|
|
151256533f | ||
|
|
3b484f58d3 | ||
|
|
d3f93746a8 | ||
|
|
f99a1cb0f3 | ||
|
|
531a769007 | ||
|
|
3e51491628 | ||
| 0b01fb20db | |||
|
|
c7a769e5da | ||
|
|
a9fd57a865 | ||
| e862f0e95f | |||
| 172597e0e6 | |||
| bcdeeed206 | |||
|
|
b6626e4f60 | ||
|
|
e1c1bb8b1b | ||
|
|
90ae51e8f2 | ||
|
|
832abbd3b1 | ||
|
|
52cc68433f | ||
|
|
f430389ee7 | ||
|
|
bded79f14d | ||
|
|
2ff44cb386 | ||
|
|
a9ade9669a | ||
|
|
f902d70006 | ||
|
|
e22d74310b | ||
|
|
e5ad12e638 | ||
|
|
b963a4564e | ||
|
|
579c125b01 | ||
|
|
84d9617375 | ||
|
|
d66e90a016 | ||
|
|
373e5440ac | ||
|
|
dec23501e1 | ||
|
|
11a4bed9aa | ||
|
|
bf4a4c6b78 | ||
|
|
b54d4b9a24 | ||
|
|
84f4771691 | ||
|
|
1dc34a02e2 | ||
|
|
3ab22818da | ||
|
|
1ab474638d | ||
|
|
eea361bf5e | ||
|
|
d9052f7105 | ||
|
|
fa00572780 | ||
|
|
636f5517b2 | ||
|
|
718da5c9be | ||
|
|
e5335ce760 | ||
| 3dbc9ea264 | |||
|
|
80da400f9c | ||
|
|
5787125bbb | ||
|
|
bc54524270 | ||
|
|
a2d53c05f6 | ||
|
|
3293a29d59 | ||
|
|
550e902bf3 | ||
|
|
84e5cc0b69 | ||
|
|
4b63882f28 | ||
|
|
fb742beae3 | ||
|
|
7ef0c80630 | ||
|
|
1f75813a4d | ||
|
|
34ad8e2347 | ||
|
|
413f14e4ae | ||
|
|
c22895d499 | ||
|
|
e63184e518 | ||
|
|
755a4541c5 | ||
|
|
05cd7edf71 | ||
|
|
5759726a89 | ||
|
|
14e7926d22 | ||
|
|
c068fe3525 | ||
|
|
c2364d9d1c | ||
|
|
6be0372257 | ||
|
|
e994ad78db | ||
|
|
49fddaa13e | ||
|
|
7448a8bfa9 | ||
|
|
166267a32f | ||
|
|
b460c2659e | ||
|
|
7ccc3ab82d | ||
|
|
adb0c898a6 | ||
|
|
b38ff09f6e | ||
|
|
d9ca8a70f0 | ||
|
|
60128ee924 | ||
|
|
b189991f9d | ||
|
|
07ffc1ffae | ||
|
|
a6afef4850 | ||
|
|
9c0c486111 | ||
|
|
3f5cf61fb6 | ||
|
|
6222902688 | ||
|
|
7febee04fa | ||
|
|
4640f0a8ae | ||
|
|
8969a952e4 | ||
|
|
bc9415bb10 | ||
|
|
cbd86ada20 | ||
|
|
0f8ea3aa36 | ||
|
|
f62f68fd66 | ||
|
|
eddafd2827 | ||
|
|
e3ce9d7f1a | ||
|
|
df96e6df0f | ||
|
|
6c573b496a | ||
|
|
1d85bc7424 | ||
|
|
f4d94b9725 | ||
|
|
4e7a18bfb4 | ||
|
|
c1ae5064b8 |
@@ -25,10 +25,14 @@ init:
|
||||
# Set clone depth (do not fetch complete history)
|
||||
clone_depth: 5
|
||||
|
||||
# Skipping commits affecting only specific files
|
||||
# Skip commits affecting only specific files
|
||||
skip_commits:
|
||||
files:
|
||||
- 'documentation/*'
|
||||
# Removed - 'documentation/*'
|
||||
# Unfortunately Appveyor only looks at the HEAD (latest) commit, so if
|
||||
# the last commit pushed to a PR only updates the Release Notes the whole
|
||||
# PR build gets skipped. GitHub Actions looks at the complete list
|
||||
# of files changed since its last build, which is much more useful.
|
||||
- 'startup/*'
|
||||
- '.github/*'
|
||||
- '.tools/*'
|
||||
@@ -68,6 +72,8 @@ environment:
|
||||
- CMP: vs2010
|
||||
- CMP: gcc
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
# TODO: static linking w/ readline isn't working. Bypass auto-detect
|
||||
COMMANDLINE_LIBRARY: EPICS
|
||||
|
||||
# Platform: processor architecture
|
||||
platform:
|
||||
|
||||
@@ -32,10 +32,14 @@ init:
|
||||
# Set clone depth (do not fetch complete history)
|
||||
clone_depth: 5
|
||||
|
||||
# Skipping commits affecting only specific files
|
||||
# Skip commits affecting only specific files
|
||||
skip_commits:
|
||||
files:
|
||||
- 'documentation/*'
|
||||
# Removed - 'documentation/*'
|
||||
# Unfortunately Appveyor only looks at the HEAD (latest) commit, so if
|
||||
# the last commit pushed to a PR only updates the Release Notes the whole
|
||||
# PR build gets skipped. GitHub Actions looks at the complete list
|
||||
# of files changed since its last build, which is much more useful.
|
||||
- 'startup/*'
|
||||
- '.github/*'
|
||||
- '.tools/*'
|
||||
@@ -74,6 +78,9 @@ environment:
|
||||
- CMP: vs2012
|
||||
- CMP: vs2010
|
||||
- CMP: gcc
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
# TODO: static linking w/ readline isn't working. Bypass auto-detect
|
||||
COMMANDLINE_LIBRARY: EPICS
|
||||
|
||||
# Platform: processor architecture
|
||||
platform:
|
||||
|
||||
2
.ci
2
.ci
Submodule .ci updated: 8a2666a9de...1e0e326f74
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@@ -0,0 +1,8 @@
|
||||
# Documentation for this file: https://EditorConfig.org
|
||||
|
||||
root = true
|
||||
|
||||
# Unix-style newlines ending every file,
|
||||
# as some compilers complain about files not ending in newline
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
13
.github/workflows/check-editorconfig.yml
vendored
Normal file
13
.github/workflows/check-editorconfig.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: Check EditorConfig
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
editorconfig:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: EditorConfig-Action
|
||||
uses: greut/eclint-action@v0
|
||||
85
.github/workflows/ci-scripts-build.yml
vendored
85
.github/workflows/ci-scripts-build.yml
vendored
@@ -43,9 +43,7 @@ jobs:
|
||||
env:
|
||||
CMP: ${{ matrix.cmp }}
|
||||
BCFG: ${{ matrix.configuration }}
|
||||
WINE: ${{ matrix.wine }}
|
||||
RTEMS: ${{ matrix.rtems }}
|
||||
RTEMS_TARGET: ${{ matrix.rtems_target }}
|
||||
CI_CROSS_TARGETS: ${{ matrix.cross }}
|
||||
EXTRA: ${{ matrix.extra }}
|
||||
TEST: ${{ matrix.test }}
|
||||
strategy:
|
||||
@@ -56,13 +54,13 @@ jobs:
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
wine: "64"
|
||||
cross: "windows-x64-mingw"
|
||||
name: "Ub-20 gcc-9 + MinGW"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: static
|
||||
wine: "64"
|
||||
cross: "windows-x64-mingw"
|
||||
name: "Ub-20 gcc-9 + MinGW, static"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
@@ -71,6 +69,12 @@ jobs:
|
||||
extra: "CMD_CXXFLAGS=-std=c++11"
|
||||
name: "Ub-20 gcc-9 C++11, static"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: static
|
||||
extra: "CMD_CFLAGS=-funsigned-char CMD_CXXFLAGS=-funsigned-char"
|
||||
name: "Ub-20 gcc-9 unsigned char"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: clang
|
||||
configuration: default
|
||||
@@ -85,67 +89,42 @@ jobs:
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
rtems: "5"
|
||||
rtems_target: RTEMS-pc686-qemu
|
||||
cross: "RTEMS-pc686-qemu@5"
|
||||
name: "Ub-20 gcc-9 + RT-5.1 pc686"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
rtems: "5"
|
||||
rtems_target: RTEMS-beatnik
|
||||
cross: "RTEMS-beatnik@5"
|
||||
test: NO
|
||||
name: "Ub-20 gcc-9 + RT-5.1 beatnik"
|
||||
|
||||
# Only build one RTEMS target per CPU family
|
||||
# unless it's running the tests
|
||||
#
|
||||
# - os: ubuntu-20.04
|
||||
# cmp: gcc
|
||||
# configuration: default
|
||||
# rtems: "5"
|
||||
# rtems_target: RTEMS-mvme3100
|
||||
# test: NO
|
||||
# name: "Ub-20 gcc-9 + RT-5.1 mvme3100"
|
||||
#
|
||||
# - os: ubuntu-20.04
|
||||
# cmp: gcc
|
||||
# configuration: default
|
||||
# rtems: "5"
|
||||
# rtems_target: RTEMS-qoriq_e500
|
||||
# test: NO
|
||||
# name: "Ub-20 gcc-9 + RT-5.1 qoriq_e500"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
rtems: "5"
|
||||
rtems_target: RTEMS-xilinx_zynq_a9_qemu
|
||||
cross: "RTEMS-xilinx_zynq_a9_qemu@5"
|
||||
test: NO
|
||||
name: "Ub-20 gcc-9 + RT-5.1 xilinx_zynq_a9_qemu"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
rtems: "5"
|
||||
rtems_target: RTEMS-uC5282
|
||||
cross: "RTEMS-uC5282@5"
|
||||
test: NO
|
||||
name: "Ub-20 gcc-9 + RT-5.1 uC5282"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
rtems: "4.10"
|
||||
name: "Ub-20 gcc-9 + RT-4.10"
|
||||
rtems_target: RTEMS-pc386-qemu
|
||||
cross: "RTEMS-pc386-qemu@4.10"
|
||||
test: NO
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
rtems: "4.9"
|
||||
name: "Ub-20 gcc-9 + RT-4.9"
|
||||
rtems_target: RTEMS-pc386-qemu
|
||||
cross: "RTEMS-pc386-qemu@4.9"
|
||||
|
||||
- os: macos-latest
|
||||
cmp: clang
|
||||
@@ -154,13 +133,15 @@ jobs:
|
||||
|
||||
- os: windows-2019
|
||||
cmp: vs2019
|
||||
configuration: default
|
||||
configuration: debug
|
||||
name: "Win2019 MSC-19"
|
||||
extra: "CMD_CXXFLAGS=-analysis"
|
||||
|
||||
- os: windows-2019
|
||||
cmp: vs2019
|
||||
configuration: static
|
||||
configuration: static-debug
|
||||
name: "Win2019 MSC-19, static"
|
||||
extra: "CMD_CXXFLAGS=-analysis"
|
||||
|
||||
- os: windows-2019
|
||||
cmp: vs2019
|
||||
@@ -172,8 +153,28 @@ jobs:
|
||||
configuration: default
|
||||
name: "Win2019 mingw"
|
||||
|
||||
# Cross builds
|
||||
|
||||
- os: ubuntu-latest
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
name: "Cross linux-aarch64"
|
||||
cross: linux-aarch64
|
||||
|
||||
- os: ubuntu-latest
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
name: "Cross linux-arm gnueabi"
|
||||
cross: linux-arm@arm-linux-gnueabi
|
||||
|
||||
- os: ubuntu-latest
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
name: "Cross linux-arm gnueabihf"
|
||||
cross: linux-arm@arm-linux-gnueabihf
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- name: Automatic core dumper analysis
|
||||
@@ -191,7 +192,7 @@ jobs:
|
||||
run: python .ci/cue.py -T 60M test
|
||||
- name: Upload tapfiles Artifact
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: tapfiles ${{ matrix.name }}
|
||||
path: '**/O.*/*.tap'
|
||||
@@ -258,7 +259,7 @@ jobs:
|
||||
# people would rather just break all existing scripts...
|
||||
[ -e /usr/bin/python ] || ln -sf python3 /usr/bin/python
|
||||
python --version
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- name: Automatic core dumper analysis
|
||||
@@ -271,7 +272,7 @@ jobs:
|
||||
run: python .ci/cue.py -T 20M test
|
||||
- name: Upload tapfiles Artifact
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: tapfiles ${{ matrix.name }}
|
||||
path: '**/O.*/*.tap'
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,6 +11,7 @@
|
||||
/modules/Makefile.local
|
||||
O.*/
|
||||
/QtC-*
|
||||
/.qtc_*
|
||||
/.vscode/
|
||||
*.orig
|
||||
*.log
|
||||
|
||||
@@ -140,10 +140,12 @@ sed \
|
||||
-e '/\/\.ci-local\//d' \
|
||||
-e '/\/\.tools\//d' \
|
||||
-e '/\/jenkins\//d' \
|
||||
-e '/\/\.git/d' \
|
||||
-e '/\/\.github\//d' \
|
||||
-e '/\/\.gitmodules$/d' \
|
||||
-e '/\/\.hgtags$/d' \
|
||||
-e '/\/\.cproject$/d' \
|
||||
-e '/\/\.project$/d' \
|
||||
-e '/\/\.lgtm\.yml$/d' \
|
||||
-e '/\/\.travis\.yml$/d' \
|
||||
-e '/\/\.appveyor\.yml$/d' \
|
||||
-e '/\/\.readthedocs\.yml$/d' \
|
||||
|
||||
@@ -38,10 +38,16 @@ TOOLS = $(abspath $(EPICS_BASE_HOST_BIN))
|
||||
FIND_TOOL = $(firstword $(wildcard $(TOOLS)/$(1) \
|
||||
$(TOP)/src/tools/$(1)) $(EPICS_BASE)/src/tools/$(1))
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# Find Perl modules for dependencies
|
||||
FIND_PM = $(wildcard $(EPICS_BASE)/lib/perl/$(1))
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# EPICS Base build tools and tool flags
|
||||
|
||||
PODTOHTML = $(PERL) $(TOOLS)/podToHtml.pl
|
||||
PODTOHTML_pl = $(TOOLS)/podToHtml.pl
|
||||
PODTOHTML_dep = $(PODTOHTML_pl) $(call FIND_PM,EPICS/PodHtml.pm)
|
||||
PODTOHTML = $(PERL) $(PODTOHTML_pl)
|
||||
CONVERTRELEASE = $(PERL) $(call FIND_TOOL,convertRelease.pl)
|
||||
FULLPATHNAME = $(PERL) $(TOOLS)/fullPathName.pl
|
||||
GENVERSIONHEADER = $(PERL) $(TOOLS)/genVersionHeader.pl $(QUIET_FLAG) $(QUESTION_FLAG)
|
||||
|
||||
@@ -52,11 +52,11 @@ EPICS_MODIFICATION = 7
|
||||
|
||||
# EPICS_PATCH_LEVEL must be a number (win32 resource file requirement)
|
||||
# Not included in the official EPICS version number if zero
|
||||
EPICS_PATCH_LEVEL = 0
|
||||
EPICS_PATCH_LEVEL = 1
|
||||
|
||||
# Immediately after an official release the EPICS_PATCH_LEVEL is incremented
|
||||
# and the -DEV suffix is added (similar to the Maven -SNAPSHOT versions)
|
||||
EPICS_DEV_SNAPSHOT=
|
||||
EPICS_DEV_SNAPSHOT=-DEV
|
||||
|
||||
# No changes should be needed below here
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
EPICS_CA_MAJOR_VERSION = 4
|
||||
EPICS_CA_MINOR_VERSION = 14
|
||||
EPICS_CA_MAINTENANCE_VERSION = 2
|
||||
EPICS_CA_MAINTENANCE_VERSION = 3
|
||||
|
||||
# Development flag, set to zero for release versions
|
||||
|
||||
EPICS_CA_DEVELOPMENT_FLAG = 0
|
||||
EPICS_CA_DEVELOPMENT_FLAG = 1
|
||||
|
||||
# Immediately after a release the MAINTENANCE_VERSION
|
||||
# will be incremented and the DEVELOPMENT_FLAG set to 1
|
||||
|
||||
@@ -5,14 +5,26 @@
|
||||
# in file LICENSE that is included with this distribution.
|
||||
#*************************************************************************
|
||||
|
||||
# Our locally-built tools
|
||||
DBEXPAND = $(PERL) $(EPICS_BASE_HOST_BIN)/dbdExpand.pl
|
||||
DBTORECORDTYPEH = $(PERL) $(EPICS_BASE_HOST_BIN)/dbdToRecordtypeH.pl
|
||||
DBTOMENUH = $(PERL) $(EPICS_BASE_HOST_BIN)/dbdToMenuH.pl
|
||||
DBDTOHTML = $(PERL) $(EPICS_BASE_HOST_BIN)/dbdToHtml.pl
|
||||
REGISTERRECORDDEVICEDRIVER = $(PERL) $(EPICS_BASE_HOST_BIN)/registerRecordDeviceDriver.pl
|
||||
# Installed perl scripts and dependent modules that have
|
||||
# a significant effect on the script's output
|
||||
DBDEXPAND_pl = $(EPICS_BASE_HOST_BIN)/dbdExpand.pl
|
||||
DBDTORECTYPEH_pl = $(EPICS_BASE_HOST_BIN)/dbdToRecordtypeH.pl
|
||||
DBDTORECTYPEH_dep = $(DBDTORECTYPEH_pl) $(call FIND_PM,DBD/Rec*.pm)
|
||||
DBDTOMENUH_pl = $(EPICS_BASE_HOST_BIN)/dbdToMenuH.pl
|
||||
DBDTOMENUH_dep = $(DBDTOMENUH_pl) $(call FIND_PM,DBD/Menu.pm)
|
||||
DBDTOHTML_pl = $(EPICS_BASE_HOST_BIN)/dbdToHtml.pl
|
||||
DBDTOHTML_dep = $(DBDTOHTML_pl) $(call FIND_PM,EPICS/Pod*Html.pm)
|
||||
REGRECDEVDRV_pl = $(EPICS_BASE_HOST_BIN)/registerRecordDeviceDriver.pl
|
||||
REGRECDEVDRV_dep = $(REGRECDEVDRV_pl)
|
||||
|
||||
# Windows can need these paths to be quoted
|
||||
# Commands for running scripts in recipes
|
||||
DBEXPAND = $(PERL) $(DBDEXPAND_pl)
|
||||
DBTORECORDTYPEH = $(PERL) $(DBDTORECTYPEH_pl)
|
||||
DBTOMENUH = $(PERL) $(DBDTOMENUH_pl)
|
||||
DBDTOHTML = $(PERL) $(DBDTOHTML_pl)
|
||||
REGISTERRECORDDEVICEDRIVER = $(PERL) $(REGRECDEVDRV_pl)
|
||||
|
||||
# Installed binary executables, quoted for running on Windows
|
||||
MAKEBPT = "$(EPICS_BASE_HOST_BIN)/makeBpt$(HOSTEXE)"
|
||||
MSI3_15 = "$(EPICS_BASE_HOST_BIN)/msi$(HOSTEXE)"
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
EPICS_DATABASE_MAJOR_VERSION = 3
|
||||
EPICS_DATABASE_MINOR_VERSION = 22
|
||||
EPICS_DATABASE_MAINTENANCE_VERSION = 0
|
||||
EPICS_DATABASE_MAINTENANCE_VERSION = 1
|
||||
|
||||
# Development flag, set to zero for release versions
|
||||
|
||||
EPICS_DATABASE_DEVELOPMENT_FLAG = 0
|
||||
EPICS_DATABASE_DEVELOPMENT_FLAG = 1
|
||||
|
||||
# Immediately after a release the MAINTENANCE_VERSION
|
||||
# will be incremented and the DEVELOPMENT_FLAG set to 1
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
EPICS_LIBCOM_MAJOR_VERSION = 3
|
||||
EPICS_LIBCOM_MINOR_VERSION = 22
|
||||
EPICS_LIBCOM_MAINTENANCE_VERSION = 0
|
||||
EPICS_LIBCOM_MAINTENANCE_VERSION = 1
|
||||
|
||||
# Development flag, set to zero for release versions
|
||||
|
||||
EPICS_LIBCOM_DEVELOPMENT_FLAG = 0
|
||||
EPICS_LIBCOM_DEVELOPMENT_FLAG = 1
|
||||
|
||||
# Immediately after a release the MAINTENANCE_VERSION
|
||||
# will be incremented and the DEVELOPMENT_FLAG set to 1
|
||||
|
||||
@@ -17,7 +17,7 @@ TEMPL_SUFFIX ?= .template
|
||||
# vpath
|
||||
|
||||
vpath %.pm $(USR_VPATH) $(SRC_DIRS) $(dir $(DBD))
|
||||
vpath %.pod $(USR_VPATH) $(SRC_DIRS) $(dir $(DBD))
|
||||
vpath %.pod $(USR_VPATH) $(SRC_DIRS) .. $(dir $(DBD))
|
||||
vpath %.dbd $(USR_VPATH) $(SRC_DIRS) $(dir $(DBD))
|
||||
vpath %.db $(USR_VPATH) $(SRC_DIRS) $(dir $(DB))
|
||||
vpath %.vdb $(USR_VPATH) $(SRC_DIRS) $(dir $(DB))
|
||||
@@ -217,32 +217,32 @@ realclean: clean
|
||||
#---------------------------------------------------------------
|
||||
# Dependency files
|
||||
|
||||
%Record.h$(DEP): $(COMMON_DIR)/%Record.dbd
|
||||
%Record.h$(DEP): $(COMMON_DIR)/%Record.dbd $(DBDTORECTYPEH_dep)
|
||||
@$(RM) $@
|
||||
@$(DBTORECORDTYPEH) -D $(DBDFLAGS) -o $(COMMONDEP_TARGET) $< > $@
|
||||
@echo "$(COMMONDEP_TARGET): ../Makefile" >> $@
|
||||
|
||||
%Record.h$(DEP): %Record.dbd
|
||||
%Record.h$(DEP): %Record.dbd $(DBDTORECTYPEH_dep)
|
||||
@$(RM) $@
|
||||
@$(DBTORECORDTYPEH) -D $(DBDFLAGS) -o $(COMMONDEP_TARGET) $< > $@
|
||||
@echo "$(COMMONDEP_TARGET): ../Makefile" >> $@
|
||||
|
||||
%Record.h$(DEP): ../%Record.dbd
|
||||
%Record.h$(DEP): ../%Record.dbd $(DBDTORECTYPEH_dep)
|
||||
@$(RM) $@
|
||||
@$(DBTORECORDTYPEH) -D $(DBDFLAGS) -o $(COMMONDEP_TARGET) $< > $@
|
||||
@echo "$(COMMONDEP_TARGET): ../Makefile" >> $@
|
||||
|
||||
menu%.h$(DEP): $(COMMON_DIR)/menu%.dbd
|
||||
menu%.h$(DEP): $(COMMON_DIR)/menu%.dbd $(DBDTOMENUH_dep)
|
||||
@$(RM) $@
|
||||
@$(DBTOMENUH) -D $(DBDFLAGS) -o $(COMMONDEP_TARGET) $< > $@
|
||||
@echo "$(COMMONDEP_TARGET): ../Makefile" >> $@
|
||||
|
||||
menu%.h$(DEP): menu%.dbd
|
||||
menu%.h$(DEP): menu%.dbd $(DBDTOMENUH_dep)
|
||||
@$(RM) $@
|
||||
@$(DBTOMENUH) -D $(DBDFLAGS) -o $(COMMONDEP_TARGET) $< > $@
|
||||
@echo "$(COMMONDEP_TARGET): ../Makefile" >> $@
|
||||
|
||||
menu%.h$(DEP): ../menu%.dbd
|
||||
menu%.h$(DEP): ../menu%.dbd $(DBDTOMENUH_dep)
|
||||
@$(RM) $@
|
||||
@$(DBTOMENUH) -D $(DBDFLAGS) -o $(COMMONDEP_TARGET) $< > $@
|
||||
@echo "$(COMMONDEP_TARGET): ../Makefile" >> $@
|
||||
@@ -261,7 +261,7 @@ menu%.h$(DEP): ../menu%.dbd
|
||||
@$(DBEXPAND) -D $(DBDFLAGS) -o $(COMMONDEP_TARGET) $< > $@
|
||||
@echo "$(COMMONDEP_TARGET): ../Makefile" >> $@
|
||||
|
||||
%.dbd$(DEP):
|
||||
%.dbd$(DEP): $($*_DBD)
|
||||
@$(RM) $@
|
||||
@$(DBEXPAND) -D $(DBDFLAGS) -o $(COMMONDEP_TARGET) $($*_DBD) > $@
|
||||
@echo "$(COMMONDEP_TARGET): ../Makefile" >> $@
|
||||
@@ -330,32 +330,32 @@ $(INSTALL_DB)/%$(TEMPL_SUFFIX): ../%$(TEMPL_SUFFIX)
|
||||
#---------------------------------------------------------------
|
||||
# INC files
|
||||
|
||||
$(COMMON_DIR)/%Record.h: $(COMMON_DIR)/%Record.dbd
|
||||
$(COMMON_DIR)/%Record.h: $(COMMON_DIR)/%Record.dbd $(DBDTORECTYPEH_dep)
|
||||
@$(RM) $(notdir $@)
|
||||
$(DBTORECORDTYPEH) $(DBDFLAGS) -o $(notdir $@) $<
|
||||
@$(MV) $(notdir $@) $@
|
||||
|
||||
$(COMMON_DIR)/%Record.h: %Record.dbd
|
||||
$(COMMON_DIR)/%Record.h: %Record.dbd $(DBDTORECTYPEH_dep)
|
||||
@$(RM) $(notdir $@)
|
||||
$(DBTORECORDTYPEH) $(DBDFLAGS) -o $(notdir $@) $<
|
||||
@$(MV) $(notdir $@) $@
|
||||
|
||||
$(COMMON_DIR)/%Record.h: ../%Record.dbd
|
||||
$(COMMON_DIR)/%Record.h: ../%Record.dbd $(DBDTORECTYPEH_dep)
|
||||
@$(RM) $(notdir $@)
|
||||
$(DBTORECORDTYPEH) $(DBDFLAGS) -o $(notdir $@) $<
|
||||
@$(MV) $(notdir $@) $@
|
||||
|
||||
$(COMMON_DIR)/menu%.h: $(COMMON_DIR)/menu%.dbd
|
||||
$(COMMON_DIR)/menu%.h: $(COMMON_DIR)/menu%.dbd $(DBDTOMENUH_dep)
|
||||
@$(RM) $(notdir $@)
|
||||
$(DBTOMENUH) $(DBDFLAGS) -o $(notdir $@) $<
|
||||
@$(MV) $(notdir $@) $@
|
||||
|
||||
$(COMMON_DIR)/menu%.h: menu%.dbd
|
||||
$(COMMON_DIR)/menu%.h: menu%.dbd $(DBDTOMENUH_dep)
|
||||
@$(RM) $(notdir $@)
|
||||
$(DBTOMENUH) $(DBDFLAGS) -o $(notdir $@) $<
|
||||
@$(MV) $(notdir $@) $@
|
||||
|
||||
$(COMMON_DIR)/menu%.h: ../menu%.dbd
|
||||
$(COMMON_DIR)/menu%.h: ../menu%.dbd $(DBDTOMENUH_dep)
|
||||
@$(RM) $(notdir $@)
|
||||
$(DBTOMENUH) $(DBDFLAGS) -o $(notdir $@) $<
|
||||
@$(MV) $(notdir $@) $@
|
||||
@@ -400,7 +400,7 @@ $(COMMON_DBDCATS):$(COMMON_DIR)/%.dbd:
|
||||
$(DBDCAT_COMMAND)
|
||||
@$(MV) $(notdir $@) $@
|
||||
|
||||
$(COMMON_DIR)/%.dbd:
|
||||
$(COMMON_DIR)/%.dbd: $($*_DBD)
|
||||
$(ECHO) "Creating dbd file $(notdir $@)"
|
||||
@$(RM) $(notdir $@)
|
||||
$(DBEXPAND) $(DBDFLAGS) -o $(notdir $@) $($*_DBD)
|
||||
@@ -430,28 +430,28 @@ $(foreach file, $(DBD_INSTALLS), $(eval $(call DBD_INSTALLS_template, $(file))))
|
||||
#---------------------------------------------------------------
|
||||
# HTML files
|
||||
|
||||
$(COMMON_DIR)/%.html: %.dbd.pod
|
||||
$(COMMON_DIR)/%.html: %.dbd.pod $(DBDTOHTML_pl)
|
||||
@$(RM) $(notdir $@)
|
||||
$(DBDTOHTML) $(DBDFLAGS) -o $(notdir $@) $<
|
||||
@$(MV) $(notdir $@) $@
|
||||
|
||||
$(COMMON_DIR)/%.html: %.pod
|
||||
$(COMMON_DIR)/%.html: %.pod $(PODTOHTML_dep)
|
||||
@$(RM) $(notdir $@)
|
||||
$(PODTOHTML) -s -s -o $(notdir $@) $<
|
||||
@$(MV) $(notdir $@) $@
|
||||
|
||||
$(COMMON_DIR)/%.html: %.pm
|
||||
$(COMMON_DIR)/%.html: %.pm $(PODTOHTML_dep)
|
||||
@$(RM) $(notdir $@)
|
||||
$(PODTOHTML) -o $(notdir $@) $<
|
||||
@$(MV) $(notdir $@) $@
|
||||
|
||||
$(COMMON_DIR)/%.html: ../%.pm
|
||||
$(COMMON_DIR)/%.html: ../%.pm $(PODTOHTML_dep)
|
||||
@$(RM) $(notdir $@)
|
||||
$(PODTOHTML) -s -o $(notdir $@) $<
|
||||
@$(MKDIR) $(dir $@)
|
||||
@$(MV) $(notdir $@) $@
|
||||
|
||||
$(COMMON_DIR)/%.html: ../%.pl
|
||||
$(COMMON_DIR)/%.html: ../%.pl $(PODTOHTML_dep)
|
||||
@$(RM) $(notdir $@)
|
||||
$(PODTOHTML) -s -o $(notdir $@) $<
|
||||
@$(MV) $(notdir $@) $@
|
||||
@@ -527,16 +527,19 @@ $(foreach file, $(DB_INSTALLS), $(eval $(call DB_INSTALLS_template, $(file))))
|
||||
#---------------------------------------------------------------
|
||||
# register record,device,driver support
|
||||
|
||||
%_registerRecordDeviceDriver.cpp: $(COMMON_DIR)/%.dbd $(EPICS_BASE_HOST_BIN)/registerRecordDeviceDriver.pl
|
||||
%_registerRecordDeviceDriver.cpp: $(COMMON_DIR)/%.dbd $(REGRECDEVDRV_dep)
|
||||
@$(RM) $@
|
||||
$(REGISTERRECORDDEVICEDRIVER) $(REGRDDFLAGS) -o $@ $< $(basename $@) $(IOCS_APPL_TOP)
|
||||
$(REGISTERRECORDDEVICEDRIVER) $(REGRDDFLAGS) -o $@ \
|
||||
$< $(basename $@) $(IOCS_APPL_TOP)
|
||||
|
||||
%_registerRecordDeviceDriver.cpp: %.dbd $(EPICS_BASE_HOST_BIN)/registerRecordDeviceDriver.pl
|
||||
%_registerRecordDeviceDriver.cpp: %.dbd $(REGRECDEVDRV_dep)
|
||||
@$(RM) $@
|
||||
$(REGISTERRECORDDEVICEDRIVER) $(REGRDDFLAGS) -o $@ $< $(basename $@) $(IOCS_APPL_TOP)
|
||||
$(REGISTERRECORDDEVICEDRIVER) $(REGRDDFLAGS) -o $@ \
|
||||
$< $(basename $@) $(IOCS_APPL_TOP)
|
||||
|
||||
%_registerRecordDeviceDriver.cpp: ../%.dbd $(EPICS_BASE_HOST_BIN)/registerRecordDeviceDriver.pl
|
||||
%_registerRecordDeviceDriver.cpp: ../%.dbd $(REGRECDEVDRV_dep)
|
||||
@$(RM) $@
|
||||
$(REGISTERRECORDDEVICEDRIVER) $(REGRDDFLAGS) -o $@ $< $(basename $@) $(IOCS_APPL_TOP)
|
||||
$(REGISTERRECORDDEVICEDRIVER) $(REGRDDFLAGS) -o $@ \
|
||||
$< $(basename $@) $(IOCS_APPL_TOP)
|
||||
|
||||
.PRECIOUS: %_registerRecordDeviceDriver.cpp
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
|
||||
# Include definitions common to all Unix targets
|
||||
include $(CONFIG)/os/CONFIG.Common.UnixCommon
|
||||
GNU = NO
|
||||
CMPLR_CLASS = clang
|
||||
CC = clang
|
||||
CCC = clang++
|
||||
|
||||
OS_CLASS = freebsd
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ GNU = NO
|
||||
#
|
||||
# Darwin shared libraries
|
||||
#
|
||||
SHRLIB_LDFLAGS = -dynamiclib -flat_namespace -undefined dynamic_lookup \
|
||||
SHRLIB_LDFLAGS = -dynamiclib -flat_namespace \
|
||||
-install_name $(abspath $(INSTALL_LIB))/$@ \
|
||||
$(addprefix -compatibility_version , $(SHRLIB_VERSION)) \
|
||||
$(addprefix -current_version , $(SHRLIB_VERSION))
|
||||
|
||||
@@ -5,3 +5,4 @@
|
||||
|
||||
#Include definitions common to unix hosts
|
||||
include $(CONFIG)/os/CONFIG.UnixCommon.Common
|
||||
CMPLR_CLASS = clang
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Definitions for freebsd-x86 host - freebsd-x86 target builds
|
||||
# Sites may override these definitions in CONFIG_SITE.freebsd-x86.freebsd-x86
|
||||
#-------------------------------------------------------
|
||||
GNU_DIR=/usr/local
|
||||
|
||||
# Include common gnu compiler definitions
|
||||
include $(CONFIG)/CONFIG.gnuCommon
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Definitions for freebsd-x86_64 host - freebsd-x86_64 target builds
|
||||
# Sites may override these definitions in CONFIG_SITE.freebsd-x86_64.freebsd-x86_64
|
||||
#-------------------------------------------------------
|
||||
GNU_DIR=/usr/local
|
||||
|
||||
# Include common gnu compiler definitions
|
||||
include $(CONFIG)/CONFIG.gnuCommon
|
||||
|
||||
@@ -3,21 +3,27 @@
|
||||
# Site specific definitions for darwin builds
|
||||
#-------------------------------------------------------
|
||||
|
||||
# Note the dir/firstword/wildcard functions below are used
|
||||
# to avoid warnings about missing directories.
|
||||
# These settings are designed for users of Homebrew.
|
||||
# Users of other third-party package managers are welcome to
|
||||
# provide patches appropriate for their manager.
|
||||
ifneq (,$(wildcard /opt/homebrew))
|
||||
# Default location on aarch64
|
||||
HOMEBREW_DIR = /opt/homebrew
|
||||
else ifneq (,$(wildcard /usr/local/Homebrew))
|
||||
# Default location on x86_64
|
||||
HOMEBREW_DIR = /usr/local
|
||||
else ifneq (,$(wildcard /opt/local/include/readline))
|
||||
# MacPorts
|
||||
READLINE_DIR = /opt/local
|
||||
endif
|
||||
|
||||
# Mix-and-match of different package systems is probably not advisable,
|
||||
# but you can try that if you like...
|
||||
|
||||
# Uncomment these definitions when using Homebrew packages:
|
||||
#OP_SYS_INCLUDES += -I/usr/local/include
|
||||
#OP_SYS_LDFLAGS += $(addprefix -L,$(dir $(firstword $(wildcard /usr/local/lib/*))))
|
||||
|
||||
# Uncomment these definitions when using DarwinPorts packages:
|
||||
#OP_SYS_INCLUDES += -I/opt/local/include
|
||||
#OP_SYS_LDFLAGS += $(addprefix -L,$(dir $(firstword $(wildcard /opt/local/lib/*))))
|
||||
|
||||
# Uncomment these definitions when using Fink packages:
|
||||
#OP_SYS_INCLUDES += -I/sw/include
|
||||
#OP_SYS_LDFLAGS += $(addprefix -L,$(dir $(firstword $(wildcard /sw/lib/*))))
|
||||
# Look for Homebrew's readline
|
||||
ifneq (,$(wildcard $(HOMEBREW_DIR)/opt/readline))
|
||||
READLINE_DIR = $(HOMEBREW_DIR)/opt/readline
|
||||
endif
|
||||
|
||||
# Use GNU readline if it's avaiilable
|
||||
ifneq (,$(wildcard $(READLINE_DIR)/include/readline/readline.h))
|
||||
INCLUDES_READLINE = -I$(READLINE_DIR)/include
|
||||
LDFLAGS_READLINE = -L$(READLINE_DIR)/lib
|
||||
endif
|
||||
|
||||
@@ -45,5 +45,5 @@ COMMANDLINE_LIBRARY ?= READLINE
|
||||
COMMANDLINE_LIBRARY ?= EPICS
|
||||
# endif
|
||||
#else
|
||||
COMMANDLINE_LIBRARY ?= EPICS
|
||||
COMMANDLINE_LIBRARY ?= $(strip $(if $(wildcard $(if $(GNU_DIR),$(GNU_DIR)/include/readline/readline.h)), READLINE, EPICS))
|
||||
#endif
|
||||
|
||||
170
documentation/ComponentReference.pod
Normal file
170
documentation/ComponentReference.pod
Normal file
@@ -0,0 +1,170 @@
|
||||
=head1 EPICS Component Reference Manual
|
||||
|
||||
This document provides reference information about the record types,
|
||||
menus, link types and channel filters included with EPICS Base.
|
||||
|
||||
Many details about the record and menu definitions are derived automatically
|
||||
from the source code at build time.
|
||||
|
||||
|
||||
=head2 Introduction and IOC Concepts
|
||||
|
||||
These links point to an external website where introductory and overview
|
||||
documentation is now being published.
|
||||
|
||||
=over
|
||||
|
||||
=item * L<Introduction to EPICS|https://docs.epics-controls.org/en/latest/guides/EPICS_Intro.html>
|
||||
|
||||
=item * L<Process Database Concepts|https://docs.epics-controls.org/en/latest/guides/EPICS_Process_Database_Concepts.html>
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head2 Record Type Definitions
|
||||
|
||||
These sections describe common aspects of the record types:
|
||||
|
||||
=over
|
||||
|
||||
=item * L<Fields Common to All Record Types|dbCommonRecord>
|
||||
|
||||
=item * L<Fields Common to Input Record Types|dbCommonInput>
|
||||
|
||||
=item * L<Fields Common to Output Record Types|dbCommonOutput>
|
||||
|
||||
=back
|
||||
|
||||
|
||||
These are the record types supplied with EPICS Base:
|
||||
|
||||
=over
|
||||
|
||||
=item * L<Analog Array Input Record (aai)|aaiRecord>
|
||||
|
||||
=item * L<Analog Array Output Record (aao)|aaoRecord>
|
||||
|
||||
=item * L<Analog Input Record (ai)|aiRecord>
|
||||
|
||||
=item * L<Analog Output Record (ao)|aoRecord>
|
||||
|
||||
=item * L<Array Subroutine Record (aSub)|aSubRecord>
|
||||
|
||||
=item * L<Binary Input Record (bi)|biRecord>
|
||||
|
||||
=item * L<Binary Output Record (bo)|boRecord>
|
||||
|
||||
=item * L<Calculation Output Record (calcout)|calcoutRecord>
|
||||
|
||||
=item * L<Calculation Record (calc)|calcRecord>
|
||||
|
||||
=item * L<Compression Record (compress)|compressRecord>
|
||||
|
||||
=item * L<Data Fanout Record (dfanout)|dfanoutRecord>
|
||||
|
||||
=item * L<Event Record (event)|eventRecord>
|
||||
|
||||
=item * L<Fanout Record (fanout)|fanoutRecord>
|
||||
|
||||
=item * L<Histogram Record (histogram)|histogramRecord>
|
||||
|
||||
=item * L<64-bit Integer Input Record (int64in)|int64inRecord>
|
||||
|
||||
=item * L<64-bit Integer Output Record (int64out)|int64outRecord>
|
||||
|
||||
=item * L<Long Input Record (longin)|longinRecord>
|
||||
|
||||
=item * L<Long Output Record (longout)|longoutRecord>
|
||||
|
||||
=item * L<Long String Input Record (lsi)|lsiRecord>
|
||||
|
||||
=item * L<Long String Output Record (lso)|lsoRecord>
|
||||
|
||||
=item * L<Multi-Bit Binary Input Direct Record (mbbiDirect)|mbbiDirectRecord>
|
||||
|
||||
=item * L<Multi-Bit Binary Input Record (mbbi)|mbbiRecord>
|
||||
|
||||
=item * L<Multi-Bit Binary Output Direct Record (mbboDirect)|mbboDirectRecord>
|
||||
|
||||
=item * L<Multi-Bit Binary Output Record (mbbo)|mbboRecord>
|
||||
|
||||
=item * L<Permissive Record (permissive)|permissiveRecord>
|
||||
|
||||
=item * L<Printf Record (printf)|printfRecord>
|
||||
|
||||
=item * L<Select Record (sel)|selRecord>
|
||||
|
||||
=item * L<Sequence Record (seq)|seqRecord>
|
||||
|
||||
=item * L<State Record (state)|stateRecord>
|
||||
|
||||
=item * L<String Input Record (stringin)|stringinRecord>
|
||||
|
||||
=item * L<String Output Record (stringout)|stringoutRecord>
|
||||
|
||||
=item * L<Sub-Array Record (subArray)|subArrayRecord>
|
||||
|
||||
=item * L<Subroutine Record (sub)|subRecord>
|
||||
|
||||
=item * L<Waveform Record (waveform)|waveformRecord>
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head2 Menu Definitions
|
||||
|
||||
Menu field choices are documented with the record type that defines them, or
|
||||
here for the global menus that are used by multiple record types:
|
||||
|
||||
=over
|
||||
|
||||
=item * L<Alarm Severity Menu|menuAlarmSevr>
|
||||
|
||||
=item * L<Alarm Status Menu|menuAlarmStat>
|
||||
|
||||
=item * L<Analog Conversions Menu|menuConvert>
|
||||
|
||||
=item * L<Field Type Menu|menuFtype>
|
||||
|
||||
=item * L<Invalid Value Output Action Menu|menuIvoa>
|
||||
|
||||
=item * L<Output Mode Select Menu|menuOmsl>
|
||||
|
||||
=item * L<Process at iocInit Menu|menuPini>
|
||||
|
||||
=item * L<Post Monitors Menu|menuPost>
|
||||
|
||||
=item * L<Priority Menu|menuPriority>
|
||||
|
||||
=item * L<Scan Menu|menuScan>
|
||||
|
||||
=item * L<Simulation Mode Menu|menuSimm>
|
||||
|
||||
=item * L<YesE<sol>No Menu|menuYesNo>
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head2 Other Components
|
||||
|
||||
EPICS Base also comes with extensible sets of server Channel Filters and IOC
|
||||
Database Link types, which are documented here:
|
||||
|
||||
=over
|
||||
|
||||
=item * L<Channel Filters|filters>
|
||||
|
||||
=item * L<IOC Database Link Types|links>
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head2 Corrections and Updates
|
||||
|
||||
Corrections to these documents can be submitted as patch files to the EPICS
|
||||
core developers, or as GitHub pull requests to the 7.0 branch of Base.
|
||||
These document sources can be found under `modules/database/src` tree, mostly
|
||||
in the `std/rec` and `ioc/db` directories in files with extension `.dbd.pod`.
|
||||
The documentation source format is a combination of the EPICS DBD file format
|
||||
with an extended version of Perl's POD (plain old documentation); run `perldoc
|
||||
pod` for details of POD.
|
||||
@@ -762,7 +762,6 @@ WARN_LOGFILE =
|
||||
INPUT = ../mainpage.dox \
|
||||
../RELEASE_NOTES.md \
|
||||
../README.md \
|
||||
../RecordReference.md \
|
||||
@TOP@/include
|
||||
|
||||
# This tag can be used to specify the character encoding of the source files
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
TOP = ..
|
||||
include $(TOP)/configure/CONFIG
|
||||
|
||||
ifeq ($(T_A),$(EPICS_HOST_ARCH))
|
||||
|
||||
DOXYGEN=doxygen
|
||||
|
||||
EXPAND = Doxyfile@
|
||||
|
||||
EXPAND_ME += EPICS_VERSION
|
||||
EXPAND_ME += EPICS_REVISION
|
||||
EXPAND_ME += EPICS_MODIFICATION
|
||||
EXPAND_ME += EPICS_PATCH_LEVEL
|
||||
EXPAND_ME += OS_CLASS CMPLR_CLASS
|
||||
|
||||
HTMLS += ComponentReference.html
|
||||
|
||||
TARGETS += doxygen
|
||||
|
||||
DOXYGEN = doxygen
|
||||
ME = documentation/O.$(T_A)/html
|
||||
GH_FILES = $(ME)/ $(ME)/.nojekyll $(ME)/*.* $(ME)/*/*.*
|
||||
|
||||
install: doxygen
|
||||
include $(TOP)/configure/RULES
|
||||
|
||||
doxygen: Doxyfile ../mainpage.dox
|
||||
doxygen: Doxyfile ../mainpage.dox $(INSTALL_HTMLS)
|
||||
$(DOXYGEN)
|
||||
rsync -av $(TOP)/html/ html/
|
||||
|
||||
@@ -29,7 +29,3 @@ commit: doxygen
|
||||
(cd $(TOP) && $(CURDIR)/../commit-gh.sh $(GH_FILES))
|
||||
|
||||
.PHONY: commit
|
||||
|
||||
endif # EPICS_HOST_ARCH
|
||||
|
||||
include $(TOP)/configure/RULES
|
||||
|
||||
@@ -11,6 +11,123 @@ has changed in each release.
|
||||
The PVA submodules each have their own individual sets of release notes which
|
||||
should also be read to understand what has changed since earlier releases.
|
||||
|
||||
**This version of EPICS has not been released yet.**
|
||||
|
||||
## Changes made on the 7.0 branch since 7.0.7
|
||||
|
||||
### Fixed leak from a non-EPICS thread on WIN32
|
||||
|
||||
On Windows targets, if a thread not created by `epicsThreadCreate*()` directly
|
||||
or indirectly calls an `epicsThread*()` function, a specific tracking struct
|
||||
is allocated. Prior to this release the allocation would not be `free()`d,
|
||||
resulting in a memory leak.
|
||||
|
||||
A similar issue on POSIX targets was previously fixed.
|
||||
|
||||
### Change compiler for FreeBSD to clang
|
||||
|
||||
The default compiler for FreeBSD targets changes from GCC to clang.
|
||||
|
||||
### Expose `dbCreateAlias` in IOC shell
|
||||
|
||||
Add a new IOC shell command `dbCreateAlias` allow record aliases to be added.
|
||||
Intended for use before `iocInit`. eg. to add an alias "bar" for a record "foo".
|
||||
|
||||
```
|
||||
dbLoadRecords("some.db") # includes: record(ai, "foo") { ...
|
||||
dbCreateAlias("foo", "bar")
|
||||
iocInit()
|
||||
```
|
||||
|
||||
### dbEvent eventsRemaining missed on cancel
|
||||
|
||||
In some cases, RSRV may queue a subscription update, but not flush it.
|
||||
This partially addresses this issue.
|
||||
|
||||
### subRecord on bad INP links
|
||||
|
||||
Previously, if a subRecord has an invalid `INP*` link, it was silently failing
|
||||
(and not running the proc function). Now the the status code returned by the
|
||||
subroutine is returned from `dbProcess()`.
|
||||
|
||||
### COMMANDLINE_LIBRARY fallback to GNU_DIR
|
||||
|
||||
Fall back to the previous behavior when searching for `readline.h` with older compilers.
|
||||
|
||||
### Search for readline installed via HomeBrew.
|
||||
|
||||
Look for `/opt/local/include/readline` on OSX.
|
||||
|
||||
### Always stop worker threads
|
||||
|
||||
The SCAN and callback threads are now stopped during normal IOC shutdown.
|
||||
|
||||
### Allow runtime bypass of free list allocator
|
||||
|
||||
The environment variable `$EPICS_FREELIST_BYPASS` may be set to `YES` to cause the `freeListLib` functions to always call directly to `malloc()`/`free()`. May be useful when troubleshooting some kinds of memory allocation bugs which would otherwise be "hidden". eg. use-after-free data races. This may also improve the results of dynamic analysis tools which are not aware of this internal free list.
|
||||
|
||||
### `compress` record enhancement
|
||||
|
||||
The compress record now supports the use of partially-filled buffers when using
|
||||
any of the N-to-one algorithms. This is achieved by setting the new field `PBUF`
|
||||
to `YES`.
|
||||
|
||||
### Extended timestamp channel filter
|
||||
|
||||
The `"ts"` filter can now retrieve the record's timestamp in several numeric
|
||||
and string formats, some of which support full nanosecond precision.
|
||||
|
||||
Hal$ caget -a test:channel
|
||||
test:channel 2021-03-11 18:23:48.265386 42
|
||||
Hal$ caget -f9 'test:channel.{"ts": {"num": "dbl"}}'
|
||||
test:channel.{"ts": {"num": "dbl"}} 984331428.265386105
|
||||
Hal$ caget 'test:channel.{"ts": {"str": "iso"}}'
|
||||
test:channel.{"ts": {"str": "iso"}} 2021-03-11T18:23:48.265386+0100
|
||||
Hal$ caget -f1 'test:channel.{"ts": {"num": "ts"}}'
|
||||
test:channel.{"ts": {"num": "ts"}} 2 984331428.0 265386163.0
|
||||
|
||||
More information is included in the filters documentation, which can be found in
|
||||
the `html/filters.html` document that is generated during the build
|
||||
|
||||
### Add conditional output (OOPT) to the longout record
|
||||
|
||||
The longout record can now be configured using its new OOPT and OOCH fields
|
||||
to (not) write to its output link depending on the contents of VAL, in a
|
||||
similar manner to the calcout record. More information can be found on the
|
||||
reference page for the longout record type that accompanies this release.
|
||||
|
||||
This functionality was suggested in
|
||||
[lp# 1398215](https://bugs.launchpad.net/epics-base/+bug/1398215) and may
|
||||
be added to other output record types if the community finds it useful,
|
||||
please send feedback about the feature to tech-talk.
|
||||
|
||||
### Tab completion for IOC shell
|
||||
|
||||
When built with optional GNU libreadline support, the interactive IOC shell
|
||||
will perform tab completion for command names as well as for some arguments
|
||||
of the built-in commands. For example, the record name argument of `dbpr`,
|
||||
and the path name argument of `cd`.
|
||||
|
||||
Externally defined commands have a limited ability to opt into completion by
|
||||
using the new `iocshArgStringRecord` and `iocshArgStringPath` argument types.
|
||||
Both function identically to `iocshArgString` but indicate how to suggest
|
||||
completion strings.
|
||||
|
||||
Builds on macOS (darwin-x86 or darwin-aarch64 targets) normally use Apple's
|
||||
libedit library in readline compatibility mode, which doesn't support the tab
|
||||
completion API that GNU readline provides. You can use Homebrew or some other
|
||||
third-party package manager to install the GNU readline package, then edit the
|
||||
configure/os/CONFIG_SITE.darwinCommon.darwinCommon file to have EPICS use the
|
||||
real thing to get tab completion in the IOC shell. The default settings in that
|
||||
file currently look for and use a Homebrew-installed readline if present.
|
||||
|
||||
### Add FMOD as CALC Expression
|
||||
|
||||
The floating point modulo function `FMOD(NUM,DEN)` has been added to the CALC
|
||||
expression engine and is available to all software using that (calc and calcout
|
||||
record types, access security library and some extensions).
|
||||
|
||||
-----
|
||||
|
||||
## EPICS Release 7.0.7
|
||||
|
||||
@@ -151,6 +268,32 @@ This release fixed the leak on POSIX targets.
|
||||
See the associated github [issue 241](https://github.com/epics-base/epics-base/issues/241)
|
||||
for WIN32 status.
|
||||
|
||||
### Fixed leak from a non-EPICS thread
|
||||
|
||||
On some targets, if a thread not created by `epicsThreadCreate*()` directly
|
||||
or indirectly calls an `epicsThread*()` function, a specific tracking struct
|
||||
is allocated.
|
||||
|
||||
Prior to this release, on POSIX and WIN32 targets, this
|
||||
allocation would not be `free()`d, resulting in a memory leak.
|
||||
|
||||
This release fixed the leak on POSIX and WIN32 targets (excluding
|
||||
MSVC before vs2012, and the WINE runtime).
|
||||
|
||||
### Fixed leak from a non-EPICS thread
|
||||
|
||||
On some targets, if a thread not created by `epicsThreadCreate*()` directly
|
||||
or indirectly calls an `epicsThread*()` function, a specific tracking struct
|
||||
is allocated.
|
||||
|
||||
Prior to this release, on POSIX and WIN32 targets, this
|
||||
struct would not be `free()`d, resulting in a memory leak.
|
||||
|
||||
This release fixed the leak on POSIX targets.
|
||||
|
||||
See the associated github [issue 241](https://github.com/epics-base/epics-base/issues/241)
|
||||
for WIN32 status.
|
||||
|
||||
### Fix `CHECK_RELEASE = WARN`
|
||||
|
||||
This now works again, it was broken in 2019 (7.0.3.1) by an errant commit.
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
# Record Reference Documentation {#recordrefmanual}
|
||||
|
||||
The documentation below for the record types and menus included with Base was
|
||||
converted from the old EPICS Wiki pages and updated. This list only includes the
|
||||
record types supplied with Base. The first two links below are to an external
|
||||
website where these original reference chapters are now being published.
|
||||
|
||||
* [Introduction to EPICS](https://docs.epics-controls.org/en/latest/guides/EPICS_Intro.html)
|
||||
* [Process Database Concepts](https://docs.epics-controls.org/en/latest/guides/EPICS_Process_Database_Concepts.html)
|
||||
* [Fields Common to All Record Types](dbCommonRecord.html)
|
||||
* [Fields Common to Input Record Types](dbCommonInput.html)
|
||||
* [Fields Common to Output Record Types](dbCommonOutput.html)
|
||||
|
||||
## Record Types
|
||||
|
||||
* [Analog Array Input Record (aai)](aaiRecord.html)
|
||||
* [Analog Array Output Record (aao)](aaoRecord.html)
|
||||
* [Analog Input Record (ai)](aiRecord.html)
|
||||
* [Analog Output Record (ao)](aoRecord.html)
|
||||
* [Array Subroutine Record (aSub)](aSubRecord.html)
|
||||
* [Binary Input Record (bi)](biRecord.html)
|
||||
* [Binary Output Record (bo)](boRecord.html)
|
||||
* [Calculation Output Record (calcout)](calcoutRecord.html)
|
||||
* [Calculation Record (calc)](calcRecord.html)
|
||||
* [Compression Record (compress)](compressRecord.html)
|
||||
* [Data Fanout Record (dfanout)](dfanoutRecord.html)
|
||||
* [Event Record (event)](eventRecord.html)
|
||||
* [Fanout Record (fanout)](fanoutRecord.html)
|
||||
* [Histogram Record (histogram)](histogramRecord.html)
|
||||
* [64bit Integer Input Record (int64in)](int64inRecord.html)
|
||||
* [64bit Integer Output Record (int64out)](int64outRecord.html)
|
||||
* [Long Input Record (longin)](longinRecord.html)
|
||||
* [Long Output Record (longout)](longoutRecord.html)
|
||||
* [Long String Input Record (lsi)](lsiRecord.html)
|
||||
* [Long String Output Record (lso)](lsoRecord.html)
|
||||
* [Multi-Bit Binary Input Direct Record (mbbiDirect)](mbbiDirectRecord.html)
|
||||
* [Multi-Bit Binary Input Record (mbbi)](mbbiRecord.html)
|
||||
* [Multi-Bit Binary Output Direct Record (mbboDirect)](mbboDirectRecord.html)
|
||||
* [Multi-Bit Binary Output Record (mbbo)](mbboRecord.html)
|
||||
* [Permissive Record (permissive)](permissiveRecord.html)
|
||||
* [Printf Record (printf)](printfRecord.html)
|
||||
* [Select Record (sel)](selRecord.html)
|
||||
* [Sequence Record (seq)](seqRecord.html)
|
||||
* [State Record (state)](stateRecord.html)
|
||||
* [String Input Record (stringin)](stringinRecord.html)
|
||||
* [String Output Record (stringout)](stringoutRecord.html)
|
||||
* [Sub-Array Record (subArray)](subArrayRecord.html)
|
||||
* [Subroutine Record (sub)](subRecord.html)
|
||||
* [Waveform Record (waveform)](waveformRecord.html)
|
||||
|
||||
## Menu Definitions
|
||||
|
||||
* [Alarm Severity Menu](menuAlarmSevr.html)
|
||||
* [Alarm Status Menu](menuAlarmStat.html)
|
||||
* [Analog Conversions Menu](menuConvert.html)
|
||||
* [Field Type Menu](menuFtype.html)
|
||||
* [Invalid Value Output Action Menu](menuIvoa.html)
|
||||
* [Output Mode Select Menu](menuOmsl.html)
|
||||
* [Process at iocInit Menu](menuPini.html)
|
||||
* [Post Monitors Menu](menuPost.html)
|
||||
* [Priority Menu](menuPriority.html)
|
||||
* [Scan Menu](menuScan.html)
|
||||
* [Simulation Mode Menu](menuSimm.html)
|
||||
* [Yes/No Menu](menuYesNo.html)
|
||||
|
||||
## Corrections and Updates
|
||||
|
||||
Corrections to these documents can be submitted as patch files to the EPICS core
|
||||
developers, or as merge requests or pull requests to the 7.0 branch of Base.
|
||||
The document sources can be found in the `modules/database/src/std/rec` and
|
||||
`modules/database/src/ioc/db` directories in files with extension `.dbd.pod`.
|
||||
The documentation source format is a combination of the EPICS DBD file format
|
||||
with an extended version of Perl's POD (plain old documentation); run `perldoc
|
||||
pod` for details of POD.
|
||||
@@ -54,8 +54,8 @@ everything that has to be done since it's so easy to miss steps.</p>
|
||||
<dd>Responsible for individual operating system platforms</dd>
|
||||
<dt><strong>Application Developers</strong></dt>
|
||||
<dd>Responsible for support modules that depend on EPICS Base.</dd>
|
||||
<dt><strong>Website Editor</strong> (Andrew Johnson)</dt>
|
||||
<dd>Responsible for the EPICS website</dd>
|
||||
<dt><strong>APS Website Editor</strong> (Andrew Johnson)</dt>
|
||||
<dd>Responsible for the APS EPICS website</dd>
|
||||
</dl>
|
||||
|
||||
<form>
|
||||
@@ -180,6 +180,9 @@ everything that has to be done since it's so easy to miss steps.</p>
|
||||
<p><b>For each external submodule</b> in turn (assuming it has not been
|
||||
tagged yet):</p>
|
||||
<ol>
|
||||
<li><tt>git grep UNRELEASED</tt> and insert the module version to any
|
||||
doxygen annotations that have a <tt>@since UNRELEASED</tt> comment.
|
||||
Commit (don't push yet).</li>
|
||||
<li>Check that the module's Release Notes have been updated to cover
|
||||
all changes; add items as necessary, and set the module version
|
||||
number and release date if appropriate. Convert to HTML and view in
|
||||
@@ -188,7 +191,7 @@ everything that has to be done since it's so easy to miss steps.</p>
|
||||
cd base-7.0/modules/<module>/documentation<br />
|
||||
pandoc -f gfm -t html -o RELEASE_NOTES.html RELEASE_NOTES.md
|
||||
</tt></blockquote>
|
||||
Commit changes (don't push yet).</li>
|
||||
Commit changes (don't push).</li>
|
||||
|
||||
<li>Edit the module's release version file
|
||||
<tt>configure/CONFIG_<i>module</i>_VERSION</tt> and its top-level
|
||||
@@ -198,7 +201,7 @@ everything that has to be done since it's so easy to miss steps.</p>
|
||||
|
||||
<li>Tag the module:
|
||||
<blockquote><tt>
|
||||
git tag -m 'ANJ: Tag for EPICS 7.0.6.1' <module-version>
|
||||
git tag -m 'ANJ: Tag for EPICS 7.0.7' <module-version>
|
||||
</tt></blockquote>
|
||||
</li>
|
||||
|
||||
@@ -267,7 +270,7 @@ everything that has to be done since it's so easy to miss steps.</p>
|
||||
<td>Tag the epics-base module in Git:
|
||||
<blockquote><tt>
|
||||
cd base-7.0<br />
|
||||
git tag -m 'ANJ: Tagged for release' R7.0.6.1
|
||||
git tag -m 'ANJ: Tagged for release' R7.0.7
|
||||
</tt></blockquote>
|
||||
<p>Don't push anything to the Launchpad repository
|
||||
yet.</p>
|
||||
@@ -302,12 +305,12 @@ everything that has to be done since it's so easy to miss steps.</p>
|
||||
files and directories that are only used for continuous integration:
|
||||
<blockquote><tt>
|
||||
cd base-7.0<br />
|
||||
./.tools/make-tar.sh R7.0.6.1 ../base-7.0.6.1.tar.gz base-7.0.6.1/
|
||||
./.tools/make-tar.sh R7.0.7 ../base-7.0.7.tar.gz base-7.0.7/
|
||||
</tt></blockquote>
|
||||
Create a GPG signature file of the tarfile as follows:
|
||||
<blockquote><tt>
|
||||
cd ..<br />
|
||||
gpg --armor --sign --detach-sig base-7.0.6.1.tar.gz
|
||||
gpg --armor --sign --detach-sig base-7.0.7.tar.gz
|
||||
</tt></blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -372,7 +375,7 @@ everything that has to be done since it's so easy to miss steps.</p>
|
||||
<td>Upload the tar file and its <tt>.asc</tt> signature file to the
|
||||
epics-controls web-server.
|
||||
<blockquote><tt>
|
||||
scp base-7.0.6.1.tar.gz base-7.0.6.1.tar.gz.asc epics-controls:download/base<br />
|
||||
scp base-7.0.7.tar.gz base-7.0.7.tar.gz.asc epics-controls:download/base<br />
|
||||
</tt></blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -5,11 +5,11 @@ Documentation index
|
||||
|
||||
@li @ref releasenotes
|
||||
@li @ref install
|
||||
@li @ref recordrefmanual
|
||||
@li <a href="ComponentReference.html">EPICS Component Reference Manual</a>
|
||||
@li <a href="filters.html">Field Modifiers and Channel Filters</a>
|
||||
@li <a href="links.html">Extensible IOC Database Links</a>
|
||||
@li <a href="CAref.html">Channel Access Reference Manual</a>
|
||||
@li <a href="filters.html">Server Side Filters Reference</a>
|
||||
@li <a href="msi.html">msi: Macro Substitution and Include Tool</a>
|
||||
@li <a href="links.html">JSON Link Types</a>
|
||||
@li <a href="CA.html">Perl 5 Interface to Channel Access</a>
|
||||
|
||||
*/
|
||||
|
||||
@@ -56,6 +56,7 @@ extern "C" void epicsStdCall ca_dump_dbr (
|
||||
|
||||
if ( INVALID_DB_REQ ( type ) ) {
|
||||
printf ( "bad DBR type %ld\n", type );
|
||||
return;
|
||||
}
|
||||
|
||||
printf ( "%s\t", dbr_text[type] );
|
||||
|
||||
@@ -150,6 +150,7 @@ sub parseDbd {
|
||||
$thisField = $1;
|
||||
$thisType = $2;
|
||||
$isAfield = 1;
|
||||
$thisSize = 1024 if $thisType =~ m/^ DBF_(IN|OUT|FWD)LINK $/x;
|
||||
}
|
||||
elsif ( m/interest \s* \( \s* (\w+) \s* \)/x ) {
|
||||
die "File format error at line $i of file\n $opt_d\n"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include "asIocRegister.h"
|
||||
|
||||
/* asSetFilename */
|
||||
static const iocshArg asSetFilenameArg0 = { "ascf",iocshArgString};
|
||||
static const iocshArg asSetFilenameArg0 = { "ascf",iocshArgStringPath};
|
||||
static const iocshArg * const asSetFilenameArgs[] = {&asSetFilenameArg0};
|
||||
static const iocshFuncDef asSetFilenameFuncDef =
|
||||
{"asSetFilename",1,asSetFilenameArgs,
|
||||
@@ -97,7 +97,7 @@ static void aspmemCallFunc(const iocshArgBuf *args)
|
||||
}
|
||||
|
||||
/* astac */
|
||||
static const iocshArg astacArg0 = { "recordname",iocshArgString};
|
||||
static const iocshArg astacArg0 = { "recordname",iocshArgStringRecord};
|
||||
static const iocshArg astacArg1 = { "user",iocshArgString};
|
||||
static const iocshArg astacArg2 = { "host",iocshArgString};
|
||||
static const iocshArg * const astacArgs[] = {&astacArg0,&astacArg1,&astacArg2};
|
||||
|
||||
@@ -13,13 +13,15 @@
|
||||
|
||||
THESE_RULES := $(IOCDIR)/db/RULES
|
||||
|
||||
dbCommon.h$(DEP): $(COMMON_DIR)/dbCommonRecord.dbd $(THESE_RULES)
|
||||
dbCommon.h$(DEP): $(COMMON_DIR)/dbCommonRecord.dbd $(THESE_RULES) \
|
||||
$(DBDTORECTYPEH_dep)
|
||||
@$(RM) $@
|
||||
@$(DBTORECORDTYPEH) -D -I ../db -I $(COMMON_DIR) -o $(COMMONDEP_TARGET) $< > $@
|
||||
|
||||
$(COMMON_DIR)/dbCommonRecord.html: ../db/dbCommon.dbd.pod
|
||||
|
||||
$(COMMON_DIR)/dbCommon.h: $(COMMON_DIR)/dbCommonRecord.dbd $(THESE_RULES)
|
||||
$(COMMON_DIR)/dbCommon.h: $(COMMON_DIR)/dbCommonRecord.dbd $(THESE_RULES) \
|
||||
$(DBDTORECTYPEH_dep)
|
||||
@$(RM) $(notdir $@)
|
||||
$(DBTORECORDTYPEH) -I ../db -I $(COMMON_DIR) -o $(notdir $@) $<
|
||||
@$(MV) $(notdir $@) $@
|
||||
|
||||
@@ -58,6 +58,7 @@ typedef struct cbQueueSet {
|
||||
int shutdown; // use atomic
|
||||
int threadsConfigured;
|
||||
int threadsRunning;
|
||||
epicsThreadId *threads;
|
||||
} cbQueueSet;
|
||||
|
||||
static cbQueueSet callbackQueue[NUM_CALLBACK_PRIORITIES];
|
||||
@@ -242,11 +243,15 @@ void callbackStop(void)
|
||||
|
||||
for (i = 0; i < NUM_CALLBACK_PRIORITIES; i++) {
|
||||
cbQueueSet *mySet = &callbackQueue[i];
|
||||
int j;
|
||||
|
||||
while (epicsAtomicGetIntT(&mySet->threadsRunning)) {
|
||||
epicsEventSignal(mySet->semWakeUp);
|
||||
epicsEventWaitWithTimeout(startStopEvent, 0.1);
|
||||
}
|
||||
for(j=0; j<mySet->threadsConfigured; j++) {
|
||||
epicsThreadMustJoin(mySet->threads[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,6 +271,8 @@ void callbackCleanup(void)
|
||||
mySet->semWakeUp = NULL;
|
||||
epicsRingPointerDelete(mySet->queue);
|
||||
mySet->queue = NULL;
|
||||
free(mySet->threads);
|
||||
mySet->threads = NULL;
|
||||
}
|
||||
|
||||
epicsTimerQueueRelease(timerQueue);
|
||||
@@ -297,17 +304,25 @@ void callbackInit(void)
|
||||
cantProceed("epicsRingPointerLockedCreate failed for %s\n",
|
||||
threadNamePrefix[i]);
|
||||
callbackQueue[i].queueOverflow = FALSE;
|
||||
|
||||
if (callbackQueue[i].threadsConfigured == 0)
|
||||
callbackQueue[i].threadsConfigured = callbackThreadsDefault;
|
||||
|
||||
callbackQueue[i].threads = callocMustSucceed(callbackQueue[i].threadsConfigured,
|
||||
sizeof(*callbackQueue[i].threads),
|
||||
"callbackInit");
|
||||
|
||||
for (j = 0; j < callbackQueue[i].threadsConfigured; j++) {
|
||||
epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
|
||||
opts.joinable = 1;
|
||||
opts.priority = threadPriority[i];
|
||||
opts.stackSize = epicsThreadStackBig;
|
||||
if (callbackQueue[i].threadsConfigured > 1 )
|
||||
sprintf(threadName, "%s-%d", threadNamePrefix[i], j);
|
||||
else
|
||||
strcpy(threadName, threadNamePrefix[i]);
|
||||
tid = epicsThreadCreate(threadName, threadPriority[i],
|
||||
epicsThreadGetStackSize(epicsThreadStackBig),
|
||||
(EPICSTHREADFUNC)callbackTask, &priorityValue[i]);
|
||||
callbackQueue[i].threads[j] = tid = epicsThreadCreateOpt(threadName,
|
||||
(EPICSTHREADFUNC)callbackTask, &priorityValue[i], &opts);
|
||||
if (tid == 0) {
|
||||
cantProceed("Failed to spawn callback thread %s\n", threadName);
|
||||
} else {
|
||||
|
||||
@@ -73,7 +73,7 @@ epicsExportAddress(int, dbAccessDebugPUTF);
|
||||
|
||||
DB_LOAD_RECORDS_HOOK_ROUTINE dbLoadRecordsHook = NULL;
|
||||
|
||||
static short mapDBFToDBR[DBF_NTYPES] = {
|
||||
static const short mapDBFToDBR[DBF_NTYPES] = {
|
||||
/* DBF_STRING => */ DBR_STRING,
|
||||
/* DBF_CHAR => */ DBR_CHAR,
|
||||
/* DBF_UCHAR => */ DBR_UCHAR,
|
||||
|
||||
@@ -220,6 +220,8 @@ DBCORE_API long dbNameToAddr(const char *pname, struct dbAddr *paddr);
|
||||
/** Initialize DBADDR from a dbEntry
|
||||
* Also handles SPC_DBADDR processing. This is really an internal
|
||||
* routine for use by dbNameToAddr() and dbChannelCreate().
|
||||
*
|
||||
* \since 7.0.2.1
|
||||
*/
|
||||
DBCORE_API long dbEntryToAddr(const struct dbEntry *pdbentry,
|
||||
struct dbAddr *paddr);
|
||||
@@ -227,6 +229,8 @@ DBCORE_API long dbEntryToAddr(const struct dbEntry *pdbentry,
|
||||
/** Initialize DBENTRY from a valid dbAddr*
|
||||
* Constant time equivalent of dbInitEntry() then dbFindRecord(),
|
||||
* and finally dbFollowAlias().
|
||||
*
|
||||
* \since 3.16.1
|
||||
*/
|
||||
DBCORE_API void dbInitEntryFromAddr(struct dbAddr *paddr,
|
||||
struct dbEntry *pdbentry);
|
||||
@@ -234,6 +238,8 @@ DBCORE_API void dbInitEntryFromAddr(struct dbAddr *paddr,
|
||||
/** Initialize DBENTRY from a valid record (dbCommon*)
|
||||
* Constant time equivalent of dbInitEntry() then dbFindRecord(),
|
||||
* and finally dbFollowAlias() when no field is specified.
|
||||
*
|
||||
* \since 3.16.1
|
||||
*/
|
||||
DBCORE_API void dbInitEntryFromRecord(struct dbCommon *prec,
|
||||
struct dbEntry *pdbentry);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# in file LICENSE that is included with this distribution.
|
||||
#*************************************************************************
|
||||
|
||||
=head1 Fields Common to Input Record Types
|
||||
=title Fields Common to Input Record Types
|
||||
|
||||
This section describes fields that are found in many input record types.
|
||||
These fields usually have the same meaning whenever they are used.
|
||||
@@ -206,3 +206,5 @@ If SIMM is not YES, NO or RAW, a SOFT alarm with a severity of INVALID is
|
||||
raised, and return status is set to -1.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -3,7 +3,7 @@
|
||||
# in file LICENSE that is included with this distribution.
|
||||
#*************************************************************************
|
||||
|
||||
=head1 Fields Common to Output Record Types
|
||||
=title Fields Common to Output Record Types
|
||||
|
||||
This section describes fields that are found in many output record types.
|
||||
These fields usually have the same meaning whenever they are used.
|
||||
@@ -237,3 +237,5 @@ If SIMM is not YES or NO, a SOFT alarm with a severity of INVALID is
|
||||
raised, and return status is set to -1.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -3,7 +3,7 @@
|
||||
# in file LICENSE that is included with this distribution.
|
||||
#*************************************************************************
|
||||
|
||||
=head1 Fields Common to All Record Types
|
||||
=title Fields Common to All Record Types
|
||||
|
||||
This section contains a description of the fields that are common to all record
|
||||
types. These fields are defined in dbCommon.dbd.
|
||||
|
||||
@@ -154,7 +154,7 @@ static long dbConstLoadScalar(struct link *plink, short dbrType, void *pbuffer)
|
||||
const char *pstr = plink->value.constantStr;
|
||||
size_t len;
|
||||
|
||||
if (!pstr)
|
||||
if (!pstr || !pstr[0])
|
||||
return S_db_badField;
|
||||
len = strlen(pstr);
|
||||
|
||||
@@ -181,7 +181,7 @@ static long dbConstLoadLS(struct link *plink, char *pbuffer, epicsUInt32 size,
|
||||
const char *pstr = plink->value.constantStr;
|
||||
long status;
|
||||
|
||||
if (!pstr)
|
||||
if (!pstr || !pstr[0])
|
||||
return S_db_badField;
|
||||
|
||||
status = dbLSConvertJSON(pstr, pbuffer, size, plen);
|
||||
@@ -197,7 +197,7 @@ static long dbConstLoadArray(struct link *plink, short dbrType, void *pbuffer,
|
||||
const char *pstr = plink->value.constantStr;
|
||||
long status;
|
||||
|
||||
if (!pstr)
|
||||
if (!pstr || !pstr[0])
|
||||
return S_db_badField;
|
||||
|
||||
/* Choice values must be numeric */
|
||||
|
||||
@@ -76,6 +76,7 @@ struct event_que {
|
||||
unsigned short quota; /* the number of assigned entries*/
|
||||
unsigned short nDuplicates; /* N events duplicated on this q */
|
||||
unsigned short nCanceled; /* the number of canceled entries */
|
||||
unsigned possibleStall;
|
||||
};
|
||||
|
||||
struct event_user {
|
||||
@@ -934,6 +935,7 @@ void db_post_single_event (dbEventSubscription event)
|
||||
static int event_read ( struct event_que *ev_que )
|
||||
{
|
||||
db_field_log *pfl;
|
||||
int notifiedRemaining = 0;
|
||||
void ( *user_sub ) ( void *user_arg, struct dbChannel *chan,
|
||||
int eventsRemaining, db_field_log *pfl );
|
||||
|
||||
@@ -955,6 +957,7 @@ static int event_read ( struct event_que *ev_que )
|
||||
|
||||
while ( ev_que->evque[ev_que->getix] != EVENTQEMPTY ) {
|
||||
struct evSubscrip *pevent = ev_que->evque[ev_que->getix];
|
||||
int eventsRemaining;
|
||||
|
||||
pfl = ev_que->valque[ev_que->getix];
|
||||
if ( pevent == &canceledEvent ) {
|
||||
@@ -977,6 +980,7 @@ static int event_read ( struct event_que *ev_que )
|
||||
|
||||
event_remove ( ev_que, ev_que->getix, EVENTQEMPTY );
|
||||
ev_que->getix = RNGINC ( ev_que->getix );
|
||||
eventsRemaining = ev_que->evque[ev_que->getix] != EVENTQEMPTY && !ev_que->nCanceled;
|
||||
|
||||
/*
|
||||
* create a local copy of the call back parameters while
|
||||
@@ -1009,7 +1013,8 @@ static int event_read ( struct event_que *ev_que )
|
||||
if (pfl) {
|
||||
/* Issue user callback */
|
||||
( *user_sub ) ( pevent->user_arg, pevent->chan,
|
||||
ev_que->evque[ev_que->getix] != EVENTQEMPTY, pfl );
|
||||
eventsRemaining, pfl );
|
||||
notifiedRemaining = eventsRemaining;
|
||||
}
|
||||
LOCKEVQUE (ev_que);
|
||||
|
||||
@@ -1036,6 +1041,11 @@ static int event_read ( struct event_que *ev_que )
|
||||
db_delete_field_log(pfl);
|
||||
}
|
||||
|
||||
if(notifiedRemaining && !ev_que->possibleStall) {
|
||||
ev_que->possibleStall = 1;
|
||||
errlogPrintf(ERL_WARNING " dbEvent possible queue stall\n");
|
||||
}
|
||||
|
||||
UNLOCKEVQUE (ev_que);
|
||||
|
||||
return DB_EVENT_OK;
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <float.h>
|
||||
@@ -24,6 +23,7 @@
|
||||
#include "dbDefs.h"
|
||||
#include "epicsConvert.h"
|
||||
#include "epicsStdlib.h"
|
||||
#include "epicsStdio.h"
|
||||
#include "errlog.h"
|
||||
#include "errMdef.h"
|
||||
|
||||
@@ -1335,24 +1335,26 @@ static long cvt_menu_st(
|
||||
epicsEnum16 *from,
|
||||
char *to,
|
||||
const dbAddr *paddr)
|
||||
{
|
||||
dbFldDes *pdbFldDes;
|
||||
dbMenu *pdbMenu;
|
||||
char **papChoiceValue;
|
||||
char *pchoice;
|
||||
{
|
||||
dbFldDes *pdbFldDes;
|
||||
dbMenu *pdbMenu;
|
||||
|
||||
if(! paddr
|
||||
|| !(pdbFldDes = paddr->pfldDes)
|
||||
|| !(pdbMenu = (dbMenu *)pdbFldDes->ftPvt)
|
||||
|| *from>=pdbMenu->nChoice
|
||||
|| !(papChoiceValue = pdbMenu->papChoiceValue)
|
||||
|| !(pchoice=papChoiceValue[*from])) {
|
||||
recGblDbaddrError(S_db_badChoice,paddr,"dbFastLinkConv(cvt_menu_st)");
|
||||
return(S_db_badChoice);
|
||||
if (!paddr ||
|
||||
!(pdbFldDes = paddr->pfldDes) ||
|
||||
!(pdbMenu = (dbMenu *)pdbFldDes->ftPvt)) {
|
||||
recGblDbaddrError(S_db_badChoice, paddr, "dbFastLinkConv(cvt_menu_st)");
|
||||
return S_db_badChoice;
|
||||
}
|
||||
strncpy(to,pchoice,MAX_STRING_SIZE);
|
||||
return(0);
|
||||
}
|
||||
|
||||
if (*from < pdbMenu->nChoice) {
|
||||
strncpy(to, pdbMenu->papChoiceValue[*from], MAX_STRING_SIZE);
|
||||
}
|
||||
else {
|
||||
/* Convert out-of-range values to numeric strings */
|
||||
epicsSnprintf(to, MAX_STRING_SIZE, "%u", *from);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Get Device to String */
|
||||
|
||||
@@ -8,10 +8,13 @@
|
||||
* in file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
|
||||
#define EPICS_PRIVATE_API
|
||||
|
||||
#include "iocsh.h"
|
||||
|
||||
#include "callback.h"
|
||||
#include "dbAccess.h"
|
||||
#include "dbStaticPvt.h"
|
||||
#include "dbBkpt.h"
|
||||
#include "dbCaTest.h"
|
||||
#include "dbEvent.h"
|
||||
@@ -28,8 +31,8 @@
|
||||
DBCORE_API extern int callbackParallelThreadsDefault;
|
||||
|
||||
/* dbLoadDatabase */
|
||||
static const iocshArg dbLoadDatabaseArg0 = { "file name",iocshArgString};
|
||||
static const iocshArg dbLoadDatabaseArg1 = { "path",iocshArgString};
|
||||
static const iocshArg dbLoadDatabaseArg0 = { "file name",iocshArgStringPath};
|
||||
static const iocshArg dbLoadDatabaseArg1 = { "path",iocshArgStringPath};
|
||||
static const iocshArg dbLoadDatabaseArg2 = { "substitutions",iocshArgString};
|
||||
static const iocshArg * const dbLoadDatabaseArgs[3] =
|
||||
{
|
||||
@@ -49,7 +52,7 @@ static void dbLoadDatabaseCallFunc(const iocshArgBuf *args)
|
||||
}
|
||||
|
||||
/* dbLoadRecords */
|
||||
static const iocshArg dbLoadRecordsArg0 = { "file name",iocshArgString};
|
||||
static const iocshArg dbLoadRecordsArg0 = { "file name",iocshArgStringPath};
|
||||
static const iocshArg dbLoadRecordsArg1 = { "substitutions",iocshArgString};
|
||||
static const iocshArg * const dbLoadRecordsArgs[2] = {&dbLoadRecordsArg0,&dbLoadRecordsArg1};
|
||||
static const iocshFuncDef dbLoadRecordsFuncDef = {
|
||||
@@ -66,28 +69,28 @@ static void dbLoadRecordsCallFunc(const iocshArgBuf *args)
|
||||
}
|
||||
|
||||
/* dbb */
|
||||
static const iocshArg dbbArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dbbArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg * const dbbArgs[1] = {&dbbArg0};
|
||||
static const iocshFuncDef dbbFuncDef = {"dbb",1,dbbArgs,
|
||||
"Add breakpoint to a lock set.\n"};
|
||||
static void dbbCallFunc(const iocshArgBuf *args) { dbb(args[0].sval);}
|
||||
|
||||
/* dbd */
|
||||
static const iocshArg dbdArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dbdArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg * const dbdArgs[1] = {&dbdArg0};
|
||||
static const iocshFuncDef dbdFuncDef = {"dbd",1,dbdArgs,
|
||||
"Remove breakpoint from a record.\n"};
|
||||
static void dbdCallFunc(const iocshArgBuf *args) { dbd(args[0].sval);}
|
||||
|
||||
/* dbc */
|
||||
static const iocshArg dbcArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dbcArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg * const dbcArgs[1] = {&dbcArg0};
|
||||
static const iocshFuncDef dbcFuncDef = {"dbc",1,dbcArgs,
|
||||
"Continue processing in a lock set.\n"};
|
||||
static void dbcCallFunc(const iocshArgBuf *args) { dbc(args[0].sval);}
|
||||
|
||||
/* dbs */
|
||||
static const iocshArg dbsArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dbsArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg * const dbsArgs[1] = {&dbsArg0};
|
||||
static const iocshFuncDef dbsFuncDef = {"dbs",1,dbsArgs,
|
||||
"Step through record processing.\n"};
|
||||
@@ -99,7 +102,7 @@ static const iocshFuncDef dbstatFuncDef = {"dbstat",0,0,
|
||||
static void dbstatCallFunc(const iocshArgBuf *args) { dbstat();}
|
||||
|
||||
/* dbp */
|
||||
static const iocshArg dbpArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dbpArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg dbpArg1 = { "interest level",iocshArgInt};
|
||||
static const iocshArg * const dbpArgs[2] = {&dbpArg0,&dbpArg1};
|
||||
static const iocshFuncDef dbpFuncDef = {"dbp",2,dbpArgs,
|
||||
@@ -108,7 +111,7 @@ static void dbpCallFunc(const iocshArgBuf *args)
|
||||
{ dbp(args[0].sval,args[1].ival);}
|
||||
|
||||
/* dbap */
|
||||
static const iocshArg dbapArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dbapArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg * const dbapArgs[1] = {&dbapArg0};
|
||||
static const iocshFuncDef dbapFuncDef = {"dbap",1,dbapArgs,
|
||||
"toggle printing after processing a certain record.\n"};
|
||||
@@ -122,7 +125,7 @@ static const iocshFuncDef dbsrFuncDef = {"dbsr",1,dbsrArgs,
|
||||
static void dbsrCallFunc(const iocshArgBuf *args) { dbsr(args[0].ival);}
|
||||
|
||||
/* dbcar */
|
||||
static const iocshArg dbcarArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dbcarArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg dbcarArg1 = { "level",iocshArgInt};
|
||||
static const iocshArg * const dbcarArgs[2] = {&dbcarArg0,&dbcarArg1};
|
||||
static const iocshFuncDef dbcarFuncDef = {"dbcar",2,dbcarArgs,
|
||||
@@ -137,7 +140,7 @@ static void dbcarCallFunc(const iocshArgBuf *args)
|
||||
}
|
||||
|
||||
/* dbjlr */
|
||||
static const iocshArg dbjlrArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dbjlrArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg dbjlrArg1 = { "level",iocshArgInt};
|
||||
static const iocshArg * const dbjlrArgs[2] = {&dbjlrArg0,&dbjlrArg1};
|
||||
static const iocshFuncDef dbjlrFuncDef = {"dbjlr",2,dbjlrArgs,
|
||||
@@ -148,7 +151,7 @@ static void dbjlrCallFunc(const iocshArgBuf *args)
|
||||
}
|
||||
|
||||
/* dbel */
|
||||
static const iocshArg dbelArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dbelArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg dbelArg1 = { "level",iocshArgInt};
|
||||
static const iocshArg * const dbelArgs[2] = {&dbelArg0,&dbelArg1};
|
||||
static const iocshFuncDef dbelFuncDef = {"dbel",2,dbelArgs,
|
||||
@@ -160,7 +163,7 @@ static void dbelCallFunc(const iocshArgBuf *args)
|
||||
}
|
||||
|
||||
/* dba */
|
||||
static const iocshArg dbaArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dbaArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg * const dbaArgs[1] = {&dbaArg0};
|
||||
static const iocshFuncDef dbaFuncDef = {"dba",1,dbaArgs,
|
||||
"dbAddr info.\n"};
|
||||
@@ -194,21 +197,21 @@ static const iocshFuncDef dbliFuncDef = {"dbli",1,dbliArgs,
|
||||
static void dbliCallFunc(const iocshArgBuf *args) { dbli(args[0].sval);}
|
||||
|
||||
/* dbla */
|
||||
static const iocshArg dblaArg0 = { "pattern",iocshArgString};
|
||||
static const iocshArg dblaArg0 = { "pattern",iocshArgStringRecord};
|
||||
static const iocshArg * const dblaArgs[1] = {&dblaArg0};
|
||||
static const iocshFuncDef dblaFuncDef = {"dbla",1,dblaArgs,
|
||||
"List record alias()s by alias name pattern.\n"};
|
||||
static void dblaCallFunc(const iocshArgBuf *args) { dbla(args[0].sval);}
|
||||
|
||||
/* dbgrep */
|
||||
static const iocshArg dbgrepArg0 = { "pattern",iocshArgString};
|
||||
static const iocshArg dbgrepArg0 = { "pattern",iocshArgStringRecord};
|
||||
static const iocshArg * const dbgrepArgs[1] = {&dbgrepArg0};
|
||||
static const iocshFuncDef dbgrepFuncDef = {"dbgrep",1,dbgrepArgs,
|
||||
"List record names matching pattern.\n"};
|
||||
static void dbgrepCallFunc(const iocshArgBuf *args) { dbgrep(args[0].sval);}
|
||||
|
||||
/* dbgf */
|
||||
static const iocshArg dbgfArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dbgfArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg * const dbgfArgs[1] = {&dbgfArg0};
|
||||
static const iocshFuncDef dbgfFuncDef = {"dbgf",1,dbgfArgs,
|
||||
"Database Get Field.\n"
|
||||
@@ -216,7 +219,7 @@ static const iocshFuncDef dbgfFuncDef = {"dbgf",1,dbgfArgs,
|
||||
static void dbgfCallFunc(const iocshArgBuf *args) { dbgf(args[0].sval);}
|
||||
|
||||
/* dbpf */
|
||||
static const iocshArg dbpfArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dbpfArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg dbpfArg1 = { "value",iocshArgString};
|
||||
static const iocshArg * const dbpfArgs[2] = {&dbpfArg0,&dbpfArg1};
|
||||
static const iocshFuncDef dbpfFuncDef = {"dbpf",2,dbpfArgs,
|
||||
@@ -226,7 +229,7 @@ static void dbpfCallFunc(const iocshArgBuf *args)
|
||||
{ dbpf(args[0].sval,args[1].sval);}
|
||||
|
||||
/* dbpr */
|
||||
static const iocshArg dbprArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dbprArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg dbprArg1 = { "interest level",iocshArgInt};
|
||||
static const iocshArg * const dbprArgs[2] = {&dbprArg0,&dbprArg1};
|
||||
static const iocshFuncDef dbprFuncDef = {"dbpr",2,dbprArgs,
|
||||
@@ -236,14 +239,14 @@ static void dbprCallFunc(const iocshArgBuf *args)
|
||||
{ dbpr(args[0].sval,args[1].ival);}
|
||||
|
||||
/* dbtr */
|
||||
static const iocshArg dbtrArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dbtrArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg * const dbtrArgs[1] = {&dbtrArg0};
|
||||
static const iocshFuncDef dbtrFuncDef = {"dbtr",1,dbtrArgs,
|
||||
"Process record and then some fields.\n"};
|
||||
static void dbtrCallFunc(const iocshArgBuf *args) { dbtr(args[0].sval);}
|
||||
|
||||
/* dbtgf */
|
||||
static const iocshArg dbtgfArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dbtgfArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg * const dbtgfArgs[1] = {&dbtgfArg0};
|
||||
static const iocshFuncDef dbtgfFuncDef = {"dbtgf",1,dbtgfArgs,
|
||||
"Database Test Get Field.\n"
|
||||
@@ -251,7 +254,7 @@ static const iocshFuncDef dbtgfFuncDef = {"dbtgf",1,dbtgfArgs,
|
||||
static void dbtgfCallFunc(const iocshArgBuf *args) { dbtgf(args[0].sval);}
|
||||
|
||||
/* dbtpf */
|
||||
static const iocshArg dbtpfArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dbtpfArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg dbtpfArg1 = { "value",iocshArgString};
|
||||
static const iocshArg * const dbtpfArgs[2] = {&dbtpfArg0,&dbtpfArg1};
|
||||
static const iocshFuncDef dbtpfFuncDef = {"dbtpf",2,dbtpfArgs,
|
||||
@@ -274,14 +277,14 @@ static const iocshFuncDef dbhcrFuncDef = {"dbhcr",0,0,
|
||||
static void dbhcrCallFunc(const iocshArgBuf *args) { dbhcr();}
|
||||
|
||||
/* gft */
|
||||
static const iocshArg gftArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg gftArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg * const gftArgs[1] = {&gftArg0};
|
||||
static const iocshFuncDef gftFuncDef = {"gft",1,gftArgs,
|
||||
"Report dbChannel info and value.\n"};
|
||||
static void gftCallFunc(const iocshArgBuf *args) { gft(args[0].sval);}
|
||||
|
||||
/* pft */
|
||||
static const iocshArg pftArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg pftArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg pftArg1 = { "value",iocshArgString};
|
||||
static const iocshArg * const pftArgs[2] = {&pftArg0,&pftArg1};
|
||||
static const iocshFuncDef pftFuncDef = {"pft",2,pftArgs,
|
||||
@@ -290,7 +293,7 @@ static void pftCallFunc(const iocshArgBuf *args)
|
||||
{ pft(args[0].sval,args[1].sval);}
|
||||
|
||||
/* dbtpn */
|
||||
static const iocshArg dbtpnArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dbtpnArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg dbtpnArg1 = { "value",iocshArgString};
|
||||
static const iocshArg * const dbtpnArgs[2] = {&dbtpnArg0,&dbtpnArg1};
|
||||
static const iocshFuncDef dbtpnFuncDef = {"dbtpn",2,dbtpnArgs,
|
||||
@@ -318,7 +321,7 @@ static void dbPutAttrCallFunc(const iocshArgBuf *args)
|
||||
{ dbPutAttribute(args[0].sval,args[1].sval,args[2].sval);}
|
||||
|
||||
/* tpn */
|
||||
static const iocshArg tpnArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg tpnArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg tpnArg1 = { "value",iocshArgString};
|
||||
static const iocshArg * const tpnArgs[2] = {&tpnArg0,&tpnArg1};
|
||||
static const iocshFuncDef tpnFuncDef = {"tpn",2,tpnArgs,
|
||||
@@ -327,7 +330,7 @@ static void tpnCallFunc(const iocshArgBuf *args)
|
||||
{ tpn(args[0].sval,args[1].sval);}
|
||||
|
||||
/* dblsr */
|
||||
static const iocshArg dblsrArg0 = { "record name",iocshArgString};
|
||||
static const iocshArg dblsrArg0 = { "record name",iocshArgStringRecord};
|
||||
static const iocshArg dblsrArg1 = { "interest level",iocshArgInt};
|
||||
static const iocshArg * const dblsrArgs[2] = {&dblsrArg0,&dblsrArg1};
|
||||
static const iocshFuncDef dblsrFuncDef = {"dblsr",2,dblsrArgs,
|
||||
@@ -500,6 +503,8 @@ static void dbStateShowAllCallFunc (const iocshArgBuf *args)
|
||||
|
||||
void dbIocRegister(void)
|
||||
{
|
||||
iocshCompleteRecord = &dbCompleteRecord;
|
||||
|
||||
iocshRegister(&dbbFuncDef,dbbCallFunc);
|
||||
iocshRegister(&dbdFuncDef,dbdCallFunc);
|
||||
iocshRegister(&dbcFuncDef,dbcCallFunc);
|
||||
|
||||
@@ -410,8 +410,21 @@ DBCORE_API long dbLoadLinkArray(struct link *, short dbrType, void *pbuffer,
|
||||
DBCORE_API long dbGetNelements(const struct link *plink, long *pnElements);
|
||||
DBCORE_API int dbIsLinkConnected(const struct link *plink); /* 0 or 1 */
|
||||
DBCORE_API int dbGetLinkDBFtype(const struct link *plink);
|
||||
/** \brief Fetch current value from link.
|
||||
* \param dbrType Database DBR code
|
||||
* \param pbuffer Destination buffer
|
||||
* \param nRequest If !NULL. Caller initializes with number of elements requested,
|
||||
* On success, set to number of elements written to pbuffer.
|
||||
* \return 0 on success
|
||||
*
|
||||
* When called with `nRequest==NULL`, treated as a request for one (1)
|
||||
* element.
|
||||
*
|
||||
* see lset::getValue
|
||||
*/
|
||||
DBCORE_API long dbTryGetLink(struct link *, short dbrType, void *pbuffer,
|
||||
long *nRequest);
|
||||
/** see dbTryGetLink() */
|
||||
DBCORE_API long dbGetLink(struct link *, short dbrType, void *pbuffer,
|
||||
long *options, long *nRequest);
|
||||
DBCORE_API long dbGetControlLimits(const struct link *plink, double *low,
|
||||
|
||||
@@ -121,8 +121,7 @@ void dbLockIncRef(lockSet* ls)
|
||||
{
|
||||
int cnt = epicsAtomicIncrIntT(&ls->refcount);
|
||||
if(cnt<=1) {
|
||||
errlogPrintf("dbLockIncRef(%p) on dead lockSet. refs: %d\n", ls, cnt);
|
||||
cantProceed(NULL);
|
||||
cantProceed("dbLockIncRef(%p) on dead lockSet. refs: %d\n", ls, cnt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,9 +144,8 @@ void dbLockDecRef(lockSet *ls)
|
||||
epicsMutexMustLock(ls->lock);
|
||||
|
||||
if(ellCount(&ls->lockRecordList)!=0) {
|
||||
errlogPrintf("dbLockDecRef(%p) would free lockSet with %d records\n",
|
||||
ls, ellCount(&ls->lockRecordList));
|
||||
cantProceed(NULL);
|
||||
cantProceed("dbLockDecRef(%p) would free lockSet with %d records\n",
|
||||
ls, ellCount(&ls->lockRecordList));
|
||||
}
|
||||
|
||||
epicsMutexUnlock(ls->lock);
|
||||
@@ -421,9 +419,8 @@ retry:
|
||||
#ifdef LOCKSET_DEBUG
|
||||
if(plock->owner) {
|
||||
if(plock->owner!=myself || plock->ownercount<1) {
|
||||
errlogPrintf("dbScanLockMany(%p) ownership violation %p (%p) %u\n",
|
||||
locker, plock->owner, myself, plock->ownercount);
|
||||
cantProceed(NULL);
|
||||
cantProceed("dbScanLockMany(%p) ownership violation %p (%p) %u\n",
|
||||
locker, plock->owner, myself, plock->ownercount);
|
||||
}
|
||||
plock->ownercount++;
|
||||
} else {
|
||||
@@ -444,8 +441,7 @@ retry:
|
||||
/* if we have at least one lockRecord, then we will always lock
|
||||
* at least its present lockSet
|
||||
*/
|
||||
errlogPrintf("dbScanLockMany(%p) didn't lock anything\n", locker);
|
||||
cantProceed(NULL);
|
||||
cantProceed("dbScanLockMany(%p) didn't lock anything\n", locker);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,17 +598,15 @@ void dbLockSetMerge(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond)
|
||||
|
||||
#ifdef LOCKSET_DEBUG
|
||||
if(locker && (A->owner!=myself || B->owner!=myself)) {
|
||||
errlogPrintf("dbLockSetMerge(%p,\"%s\",\"%s\") ownership violation %p %p (%p)\n",
|
||||
locker, pfirst->name, psecond->name,
|
||||
A->owner, B->owner, myself);
|
||||
cantProceed(NULL);
|
||||
cantProceed("dbLockSetMerge(%p,\"%s\",\"%s\") ownership violation %p %p (%p)\n",
|
||||
locker, pfirst->name, psecond->name,
|
||||
A->owner, B->owner, myself);
|
||||
}
|
||||
#endif
|
||||
if(locker && (A->ownerlocker!=locker || B->ownerlocker!=locker)) {
|
||||
errlogPrintf("dbLockSetMerge(%p,\"%s\",\"%s\") locker ownership violation %p %p (%p)\n",
|
||||
locker, pfirst->name, psecond->name,
|
||||
A->ownerlocker, B->ownerlocker, locker);
|
||||
cantProceed(NULL);
|
||||
cantProceed("dbLockSetMerge(%p,\"%s\",\"%s\") locker ownership violation %p %p (%p)\n",
|
||||
locker, pfirst->name, psecond->name,
|
||||
A->ownerlocker, B->ownerlocker, locker);
|
||||
}
|
||||
|
||||
if(A==B)
|
||||
@@ -688,19 +682,17 @@ void dbLockSetSplit(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond)
|
||||
|
||||
#ifdef LOCKSET_DEBUG
|
||||
if(ls->owner!=myself || psecond->lset->plockSet->owner!=myself) {
|
||||
errlogPrintf("dbLockSetSplit(%p,\"%s\",\"%s\") ownership violation %p %p (%p)\n",
|
||||
locker, pfirst->name, psecond->name,
|
||||
ls->owner, psecond->lset->plockSet->owner, myself);
|
||||
cantProceed(NULL);
|
||||
cantProceed("dbLockSetSplit(%p,\"%s\",\"%s\") ownership violation %p %p (%p)\n",
|
||||
locker, pfirst->name, psecond->name,
|
||||
ls->owner, psecond->lset->plockSet->owner, myself);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* lockset consistency violation */
|
||||
if(ls!=psecond->lset->plockSet) {
|
||||
errlogPrintf("dbLockSetSplit(%p,\"%s\",\"%s\") consistency violation %p %p\n",
|
||||
locker, pfirst->name, psecond->name,
|
||||
pfirst->lset->plockSet, psecond->lset->plockSet);
|
||||
cantProceed(NULL);
|
||||
cantProceed("dbLockSetSplit(%p,\"%s\",\"%s\") consistency violation %p %p\n",
|
||||
locker, pfirst->name, psecond->name,
|
||||
pfirst->lset->plockSet, psecond->lset->plockSet);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -168,9 +168,13 @@ void scanStop(void)
|
||||
epicsEventSignal(ppsl->loopEvent);
|
||||
epicsEventWait(startStopEvent);
|
||||
}
|
||||
for (i = 0; i < nPeriodic; i++) {
|
||||
epicsThreadMustJoin(periodicTaskId[i]);
|
||||
}
|
||||
|
||||
scanOnce((dbCommon *)&exitOnce);
|
||||
epicsEventWait(startStopEvent);
|
||||
epicsThreadMustJoin(onceTaskId);
|
||||
}
|
||||
|
||||
void scanCleanup(void)
|
||||
@@ -761,14 +765,16 @@ void scanOnceQueueShow(const int reset)
|
||||
|
||||
static void initOnce(void)
|
||||
{
|
||||
epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
|
||||
opts.joinable = 1;
|
||||
opts.priority = epicsThreadPriorityScanLow + nPeriodic;
|
||||
opts.stackSize = epicsThreadStackBig;
|
||||
if ((onceQ = epicsRingBytesLockedCreate(sizeof(onceEntry)*onceQueueSize)) == NULL) {
|
||||
cantProceed("initOnce: Ring buffer create failed\n");
|
||||
}
|
||||
if(!onceSem)
|
||||
onceSem = epicsEventMustCreate(epicsEventEmpty);
|
||||
onceTaskId = epicsThreadCreate("scanOnce",
|
||||
epicsThreadPriorityScanLow + nPeriodic,
|
||||
epicsThreadGetStackSize(epicsThreadStackBig), onceTask, 0);
|
||||
onceTaskId = epicsThreadCreateOpt("scanOnce", onceTask, 0, &opts);
|
||||
|
||||
epicsEventWait(startStopEvent);
|
||||
}
|
||||
@@ -932,14 +938,16 @@ static void spawnPeriodic(int ind)
|
||||
{
|
||||
periodic_scan_list *ppsl = papPeriodic[ind];
|
||||
char taskName[20];
|
||||
epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
|
||||
opts.joinable = 1;
|
||||
opts.priority = epicsThreadPriorityScanLow + ind;
|
||||
opts.stackSize = epicsThreadStackBig;
|
||||
|
||||
if (!ppsl) return;
|
||||
|
||||
sprintf(taskName, "scan-%g", ppsl->period);
|
||||
periodicTaskId[ind] = epicsThreadCreate(
|
||||
taskName, epicsThreadPriorityScanLow + ind,
|
||||
epicsThreadGetStackSize(epicsThreadStackBig),
|
||||
periodicTask, (void *)ppsl);
|
||||
periodicTaskId[ind] = epicsThreadCreateOpt(
|
||||
taskName, periodicTask, (void *)ppsl, &opts);
|
||||
|
||||
epicsEventWait(startStopEvent);
|
||||
}
|
||||
|
||||
@@ -339,7 +339,7 @@ void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsign
|
||||
break;
|
||||
}
|
||||
#define OP(DBR,Type,pat) case DBR: {Type expect = *(Type*)pbuf, actual = *(Type*)gbuf; assert(vSize==sizeof(Type)); match &= expect==actual; \
|
||||
if(expect!=actual) testDiag("[%lu] expected=" pat " actual=" pat, n, expect, actual); break;}
|
||||
if(expect!=actual) {testDiag("[%lu] expected=" pat " actual=" pat, n, expect, actual);} break;}
|
||||
|
||||
OP(DBR_CHAR, char, "%c");
|
||||
OP(DBR_UCHAR, unsigned char, "%u");
|
||||
|
||||
@@ -105,7 +105,7 @@ DBCORE_API void testdbVGetFieldEqual(const char* pv, short dbrType, va_list ap);
|
||||
*
|
||||
* @code
|
||||
* static const epicsUInt32 putval[] = {1,2,3};
|
||||
* testdbVGetFieldEqual("some:wf", DBF_ULONG, NELEMENTS(putval), putval);
|
||||
* testdbPutArrFieldOk("some:wf", DBF_ULONG, NELEMENTS(putval), putval);
|
||||
* @endcode
|
||||
*
|
||||
* @see @ref dbtestactions
|
||||
@@ -190,6 +190,7 @@ DBCORE_API void testGlobalUnlock(void);
|
||||
*
|
||||
* @code
|
||||
* #include <dbUnitTest.h>
|
||||
* #include <testMain.h>
|
||||
*
|
||||
* int mytest_registerRecordDeviceDriver(DBBASE *pbase);
|
||||
* void testCase(void) {
|
||||
@@ -204,7 +205,7 @@ DBCORE_API void testGlobalUnlock(void);
|
||||
* }
|
||||
*
|
||||
* MAIN(mytestmain) {
|
||||
* testPlan(0);
|
||||
* testPlan(0); // adjust number of tests
|
||||
* testCase();
|
||||
* testCase(); // may be repeated if desirable.
|
||||
* return testDone();
|
||||
@@ -212,6 +213,9 @@ DBCORE_API void testGlobalUnlock(void);
|
||||
* @endcode
|
||||
*
|
||||
* @code
|
||||
* TOP = ..
|
||||
* include $(TOP)/configure/CONFIG
|
||||
*
|
||||
* TARGETS += $(COMMON_DIR)/mytest.dbd
|
||||
* DBDDEPENDS_FILES += mytest.dbd$(DEP)
|
||||
* TESTFILES += $(COMMON_DIR)/mytest.dbd
|
||||
@@ -222,6 +226,8 @@ DBCORE_API void testGlobalUnlock(void);
|
||||
* mytest_SRCS += mytestmain.c # see above
|
||||
* mytest_SRCS += mytest_registerRecordDeviceDriver.cpp
|
||||
* TESTFILES += some.db
|
||||
*
|
||||
* include $(TOP)/configure/RULES
|
||||
* @endcode
|
||||
*
|
||||
* @section dbtestactions Actions
|
||||
|
||||
@@ -39,12 +39,17 @@ extern "C" {
|
||||
* will adjust automatically, it just compares field sizes.
|
||||
*/
|
||||
union native_value {
|
||||
epicsInt8 dbf_char;
|
||||
epicsInt16 dbf_short;
|
||||
epicsEnum16 dbf_enum;
|
||||
epicsInt32 dbf_long;
|
||||
epicsFloat32 dbf_float;
|
||||
epicsFloat64 dbf_double;
|
||||
epicsInt8 dbf_char;
|
||||
epicsUInt8 dbf_uchar;
|
||||
epicsInt16 dbf_short;
|
||||
epicsUInt16 dbf_ushort;
|
||||
epicsEnum16 dbf_enum;
|
||||
epicsInt32 dbf_long;
|
||||
epicsUInt32 dbf_ulong;
|
||||
epicsInt64 dbf_int64;
|
||||
epicsUInt64 dbf_uint64;
|
||||
epicsFloat32 dbf_float;
|
||||
epicsFloat64 dbf_double;
|
||||
#ifdef DB_EVENT_LOG_STRINGS
|
||||
char dbf_string[MAX_STRING_SIZE];
|
||||
#endif
|
||||
|
||||
@@ -75,7 +75,7 @@ DBCORE_API void recGblInheritSevr(int msMode, void *precord, epicsEnum16 stat,
|
||||
epicsEnum16 sevr);
|
||||
DBCORE_API int recGblSetSevrMsg(void *precord, epicsEnum16 new_stat,
|
||||
epicsEnum16 new_sevr,
|
||||
const char *msg, ...) EPICS_PRINTF_STYLE(4,5);
|
||||
EPICS_PRINTF_FMT(const char *msg), ...) EPICS_PRINTF_STYLE(4,5);
|
||||
DBCORE_API int recGblSetSevrVMsg(void *precord, epicsEnum16 new_stat,
|
||||
epicsEnum16 new_sevr,
|
||||
const char *msg, va_list args);
|
||||
|
||||
@@ -28,5 +28,6 @@ dbCore_SRCS += dbYacc.c
|
||||
dbCore_SRCS += dbPvdLib.c
|
||||
dbCore_SRCS += dbStaticRun.c
|
||||
dbCore_SRCS += dbStaticIocRegister.c
|
||||
dbCore_SRCS += dbCompleteRecord.cpp
|
||||
|
||||
CLEANS += dbLex.c dbYacc.c
|
||||
|
||||
163
modules/database/src/ioc/dbStatic/dbCompleteRecord.cpp
Normal file
163
modules/database/src/ioc/dbStatic/dbCompleteRecord.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2022 Michael Davidsaver
|
||||
* SPDX-License-Identifier: EPICS
|
||||
* EPICS Base is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <epicsStdio.h>
|
||||
#include <dbAccess.h>
|
||||
#include "dbStaticPvt.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// immutable C string slice. (avoid allocating many temporary std::string)
|
||||
class CStr {
|
||||
const char* p;
|
||||
size_t l;
|
||||
public:
|
||||
CStr() :p(NULL), l(0u) {}
|
||||
CStr(const CStr& o) :p(o.p), l(o.l) {}
|
||||
explicit CStr(const char* p) :p(p), l(p ? strlen(p) : 0u) {}
|
||||
CStr(const char* p, size_t n) :p(p), l(n) {}
|
||||
|
||||
CStr& operator=(const CStr& o) {
|
||||
p = o.p;
|
||||
l = o.l;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const CStr& o) const {
|
||||
return l==o.l && (p==o.p || memcmp(p, o.p, l)==0);
|
||||
}
|
||||
bool operator!=(const CStr& o) const {
|
||||
return !(*this==o);
|
||||
}
|
||||
bool operator<(const CStr& o) const {
|
||||
size_t pl = std::min(l, o.l);
|
||||
int cmp = memcmp(p, o.p, pl);
|
||||
return cmp<0 || (cmp==0 && l < o.l);
|
||||
}
|
||||
|
||||
bool empty() const { return !l; }
|
||||
size_t size() const { return l; }
|
||||
|
||||
bool prefixOf(const CStr& full) const {
|
||||
return full.l >= l && memcmp(full.p, p, l)==0;
|
||||
}
|
||||
|
||||
CStr commonPrefix(const CStr& o, size_t startFrom=0u) const {
|
||||
size_t n, N;
|
||||
for(n=startFrom, N=std::min(l, o.l); n<N; n++) {
|
||||
if(p[n]!=o.p[n])
|
||||
break;
|
||||
}
|
||||
return CStr(p, n);
|
||||
}
|
||||
|
||||
void chop_at_first_of(const char* sep, size_t startFrom=0u) {
|
||||
size_t n;
|
||||
for(n=startFrom; n<l; n++) {
|
||||
char c = p[n];
|
||||
for(const char *s = sep; *s; s++) {
|
||||
if(c == *s) {
|
||||
l = n+1u; // include trailing separator
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char* dup() const {
|
||||
char* ret = (char*)malloc(l+1u);
|
||||
if(ret) {
|
||||
memcpy(ret, p, l);
|
||||
ret[l] = '\0';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
char** dbCompleteRecord(const char *cword)
|
||||
{
|
||||
const CStr word(cword);
|
||||
|
||||
DBENTRY ent;
|
||||
|
||||
dbInitEntry(pdbbase, &ent);
|
||||
try{
|
||||
// iterating all record twice...
|
||||
|
||||
CStr prefix;
|
||||
bool first = true;
|
||||
|
||||
// find longest prefix match
|
||||
for(long status = dbFirstRecordType(&ent); !status; status = dbNextRecordType(&ent)) {
|
||||
for(status = dbFirstRecord(&ent); !status; status = dbNextRecord(&ent)) {
|
||||
const CStr name(ent.precnode->recordname);
|
||||
|
||||
if(!word.prefixOf(name))
|
||||
continue;
|
||||
|
||||
if(first) { // first match
|
||||
prefix = name;
|
||||
first = false;
|
||||
|
||||
} else {
|
||||
prefix = prefix.commonPrefix(name, word.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// with prefix size known, iterate again to find suggestions
|
||||
typedef std::set<CStr> suggestions_t;
|
||||
suggestions_t suggestions;
|
||||
|
||||
for(long status = dbFirstRecordType(&ent); !status; status = dbNextRecordType(&ent)) {
|
||||
for(status = dbFirstRecord(&ent); !status; status = dbNextRecord(&ent)) {
|
||||
CStr name(ent.precnode->recordname);
|
||||
|
||||
if(!prefix.prefixOf(name))
|
||||
continue;
|
||||
|
||||
name.chop_at_first_of(":<>{}-", prefix.size());
|
||||
suggestions.insert(name);
|
||||
}
|
||||
}
|
||||
|
||||
dbFinishEntry(&ent);
|
||||
|
||||
char** ret = NULL;
|
||||
|
||||
if(!prefix.empty() || !suggestions.empty()) {
|
||||
ret = (char**)malloc(sizeof(*ret)*(2u + suggestions.size()));
|
||||
if(ret) {
|
||||
ret[0] = prefix.dup();
|
||||
size_t n=1u;
|
||||
for(suggestions_t::iterator it(suggestions.begin()), end(suggestions.end());
|
||||
it!=end; ++it)
|
||||
{
|
||||
ret[n++] = it->dup();
|
||||
}
|
||||
ret[n] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
} catch(std::exception& e){
|
||||
fprintf(stderr, "dbCompleteRecord error: %s\n", e.what());
|
||||
dbFinishEntry(&ent);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,7 +15,8 @@ doublequote "\""
|
||||
comment "#"
|
||||
whitespace [ \t\r\n]
|
||||
escape {backslash}.
|
||||
stringchar [^"\n\\]
|
||||
sqschar [^'\n\\]
|
||||
dqschar [^"\n\\]
|
||||
bareword [a-zA-Z0-9_\-+:.\[\]<>;]
|
||||
|
||||
punctuation [:,\[\]{}]
|
||||
@@ -84,7 +85,7 @@ static int yyreset(void)
|
||||
return(tokenSTRING);
|
||||
}
|
||||
|
||||
{doublequote}({stringchar}|{escape})*{doublequote} { /* quoted string */
|
||||
{doublequote}({dqschar}|{escape})*{doublequote} { /* quoted string */
|
||||
yylval.Str = dbmfStrdup((char *) yytext+1);
|
||||
yylval.Str[strlen(yylval.Str)-1] = '\0';
|
||||
return(tokenSTRING);
|
||||
@@ -129,14 +130,14 @@ static int yyreset(void)
|
||||
|
||||
/* Error patterns */
|
||||
|
||||
{doublequote}({stringchar}|{escape})*{newline} {
|
||||
{doublequote}({dqschar}|{escape})*{newline} {
|
||||
yyerrorAbort("Newline in string, closing quote missing");
|
||||
}
|
||||
|
||||
<JSON>{doublequote}({stringchar}|{escape})*{doublequote} {
|
||||
<JSON>{doublequote}({dqschar}|{escape})*{doublequote} {
|
||||
yyerrorAbort("Bad character in JSON string");
|
||||
}
|
||||
<JSON>{singlequote}({stringchar}|{escape})*{singlequote} {
|
||||
<JSON>{singlequote}({sqschar}|{escape})*{singlequote} {
|
||||
yyerrorAbort("Bad character in JSON string");
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,8 @@ typedef struct inputFile{
|
||||
static ELLLIST inputFileList = ELLLIST_INIT;
|
||||
|
||||
static inputFile *pinputFileNow = NULL;
|
||||
static DBBASE *pdbbase = NULL;
|
||||
/* The DBBASE most recently allocated/used by dbReadCOM() */
|
||||
static DBBASE *savedPdbbase = NULL;
|
||||
|
||||
typedef struct tempListNode {
|
||||
ELLNODE node;
|
||||
@@ -233,15 +234,15 @@ static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp,
|
||||
}
|
||||
|
||||
if(*ppdbbase == 0) *ppdbbase = dbAllocBase();
|
||||
pdbbase = *ppdbbase;
|
||||
savedPdbbase = *ppdbbase;
|
||||
if(path && strlen(path)>0) {
|
||||
dbPath(pdbbase,path);
|
||||
dbPath(savedPdbbase,path);
|
||||
} else {
|
||||
penv = getenv("EPICS_DB_INCLUDE_PATH");
|
||||
if(penv) {
|
||||
dbPath(pdbbase,penv);
|
||||
dbPath(savedPdbbase,penv);
|
||||
} else {
|
||||
dbPath(pdbbase,".");
|
||||
dbPath(savedPdbbase,".");
|
||||
}
|
||||
}
|
||||
my_buffer = dbCalloc(MY_BUFFER_SIZE,sizeof(char));
|
||||
@@ -271,7 +272,7 @@ static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp,
|
||||
FILE *fp1 = 0;
|
||||
|
||||
if (pinputFile->filename)
|
||||
pinputFile->path = dbOpenFile(pdbbase, pinputFile->filename, &fp1);
|
||||
pinputFile->path = dbOpenFile(savedPdbbase, pinputFile->filename, &fp1);
|
||||
if (!pinputFile->filename || !fp1) {
|
||||
errPrintf(0, __FILE__, __LINE__,
|
||||
"dbRead opening file %s\n",pinputFile->filename);
|
||||
@@ -297,13 +298,13 @@ static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp,
|
||||
while (ellCount(&tempList))
|
||||
popFirstTemp(); /* Memory leak on parser failure */
|
||||
|
||||
dbFreePath(pdbbase);
|
||||
dbFreePath(savedPdbbase);
|
||||
if(!status) { /*add RTYP and VERS as an attribute */
|
||||
DBENTRY dbEntry;
|
||||
DBENTRY *pdbEntry = &dbEntry;
|
||||
long localStatus;
|
||||
|
||||
dbInitEntry(pdbbase,pdbEntry);
|
||||
dbInitEntry(savedPdbbase,pdbEntry);
|
||||
localStatus = dbFirstRecordType(pdbEntry);
|
||||
while(!localStatus) {
|
||||
localStatus = dbPutRecordAttribute(pdbEntry,"RTYP",
|
||||
@@ -323,7 +324,7 @@ static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp,
|
||||
cleanup:
|
||||
if(dbRecordsAbcSorted) {
|
||||
ELLNODE *cur;
|
||||
for(cur = ellFirst(&pdbbase->recordTypeList); cur; cur=ellNext(cur))
|
||||
for(cur = ellFirst(&savedPdbbase->recordTypeList); cur; cur=ellNext(cur))
|
||||
{
|
||||
dbRecordType *rtype = CONTAINER(cur, dbRecordType, node);
|
||||
|
||||
@@ -416,12 +417,12 @@ static void dbIncludePrint(void)
|
||||
|
||||
static void dbPathCmd(char *path)
|
||||
{
|
||||
dbPath(pdbbase,path);
|
||||
dbPath(savedPdbbase,path);
|
||||
}
|
||||
|
||||
static void dbAddPathCmd(char *path)
|
||||
{
|
||||
dbAddPath(pdbbase,path);
|
||||
dbAddPath(savedPdbbase,path);
|
||||
}
|
||||
|
||||
static void dbIncludeNew(char *filename)
|
||||
@@ -431,7 +432,7 @@ static void dbIncludeNew(char *filename)
|
||||
|
||||
pinputFile = dbCalloc(1,sizeof(inputFile));
|
||||
pinputFile->filename = macEnvExpand(filename);
|
||||
pinputFile->path = dbOpenFile(pdbbase, pinputFile->filename, &fp);
|
||||
pinputFile->path = dbOpenFile(savedPdbbase, pinputFile->filename, &fp);
|
||||
if (!fp) {
|
||||
epicsPrintf("Can't open include file \"%s\"\n", filename);
|
||||
yyerror(NULL);
|
||||
@@ -453,7 +454,7 @@ static void dbMenuHead(char *name)
|
||||
yyerrorAbort("dbMenuHead: Menu name can't be empty");
|
||||
return;
|
||||
}
|
||||
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->menuList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->menuList);
|
||||
if(pgphentry) {
|
||||
duplicate = TRUE;
|
||||
return;
|
||||
@@ -501,14 +502,14 @@ static void dbMenuBody(void)
|
||||
}
|
||||
if(ellCount(&tempList)) yyerrorAbort("dbMenuBody: tempList not empty");
|
||||
/* Add menu in sorted order */
|
||||
pMenu = (dbMenu *)ellFirst(&pdbbase->menuList);
|
||||
pMenu = (dbMenu *)ellFirst(&savedPdbbase->menuList);
|
||||
while(pMenu && strcmp(pMenu->name,pnewMenu->name) >0 )
|
||||
pMenu = (dbMenu *)ellNext(&pMenu->node);
|
||||
if(pMenu)
|
||||
ellInsert(&pdbbase->menuList,ellPrevious(&pMenu->node),&pnewMenu->node);
|
||||
ellInsert(&savedPdbbase->menuList,ellPrevious(&pMenu->node),&pnewMenu->node);
|
||||
else
|
||||
ellAdd(&pdbbase->menuList,&pnewMenu->node);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash,pnewMenu->name,&pdbbase->menuList);
|
||||
ellAdd(&savedPdbbase->menuList,&pnewMenu->node);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash,pnewMenu->name,&savedPdbbase->menuList);
|
||||
if(!pgphentry) {
|
||||
yyerrorAbort("gphAdd failed");
|
||||
} else {
|
||||
@@ -525,14 +526,14 @@ static void dbRecordtypeHead(char *name)
|
||||
yyerrorAbort("dbRecordtypeHead: Recordtype name can't be empty");
|
||||
return;
|
||||
}
|
||||
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->recordTypeList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->recordTypeList);
|
||||
if(pgphentry) {
|
||||
duplicate = TRUE;
|
||||
return;
|
||||
}
|
||||
pdbRecordType = dbCalloc(1,sizeof(dbRecordType));
|
||||
pdbRecordType->name = epicsStrDup(name);
|
||||
if (pdbbase->loadCdefs) ellInit(&pdbRecordType->cdefList);
|
||||
if (savedPdbbase->loadCdefs) ellInit(&pdbRecordType->cdefList);
|
||||
if(ellCount(&tempList))
|
||||
yyerrorAbort("dbRecordtypeHead tempList not empty");
|
||||
allocTemp(pdbRecordType);
|
||||
@@ -564,13 +565,13 @@ static short findOrAddGuiGroup(const char *name)
|
||||
{
|
||||
dbGuiGroup *pdbGuiGroup;
|
||||
GPHENTRY *pgphentry;
|
||||
pgphentry = gphFind(pdbbase->pgpHash, name, &pdbbase->guiGroupList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash, name, &savedPdbbase->guiGroupList);
|
||||
if (!pgphentry) {
|
||||
pdbGuiGroup = dbCalloc(1,sizeof(dbGuiGroup));
|
||||
pdbGuiGroup->name = epicsStrDup(name);
|
||||
ellAdd(&pdbbase->guiGroupList, &pdbGuiGroup->node);
|
||||
pdbGuiGroup->key = ellCount(&pdbbase->guiGroupList);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash, pdbGuiGroup->name, &pdbbase->guiGroupList);
|
||||
ellAdd(&savedPdbbase->guiGroupList, &pdbGuiGroup->node);
|
||||
pdbGuiGroup->key = ellCount(&savedPdbbase->guiGroupList);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash, pdbGuiGroup->name, &savedPdbbase->guiGroupList);
|
||||
pgphentry->userPvt = pdbGuiGroup;
|
||||
}
|
||||
return ((dbGuiGroup *)pgphentry->userPvt)->key;
|
||||
@@ -653,8 +654,8 @@ static void dbRecordtypeFieldItem(char *name,char *value)
|
||||
return;
|
||||
}
|
||||
if(strcmp(name,"menu")==0) {
|
||||
pdbFldDes->ftPvt = (dbMenu *)dbFindMenu(pdbbase,value);
|
||||
if(!pdbbase->ignoreMissingMenus && !pdbFldDes->ftPvt)
|
||||
pdbFldDes->ftPvt = (dbMenu *)dbFindMenu(savedPdbbase,value);
|
||||
if(!savedPdbbase->ignoreMissingMenus && !pdbFldDes->ftPvt)
|
||||
yyerrorAbort("menu not found");
|
||||
return;
|
||||
}
|
||||
@@ -672,7 +673,7 @@ static void dbRecordtypeCdef(char *text) {
|
||||
tempListNode *ptempListNode;
|
||||
dbRecordType *pdbRecordType;
|
||||
|
||||
if (!pdbbase->loadCdefs || duplicate) return;
|
||||
if (!savedPdbbase->loadCdefs || duplicate) return;
|
||||
ptempListNode = (tempListNode *)ellFirst(&tempList);
|
||||
pdbRecordType = ptempListNode->item;
|
||||
|
||||
@@ -781,14 +782,14 @@ static void dbRecordtypeBody(void)
|
||||
ellInit(&pdbRecordType->attributeList);
|
||||
ellInit(&pdbRecordType->recList);
|
||||
ellInit(&pdbRecordType->devList);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash,pdbRecordType->name,
|
||||
&pdbbase->recordTypeList);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash,pdbRecordType->name,
|
||||
&savedPdbbase->recordTypeList);
|
||||
if(!pgphentry) {
|
||||
yyerrorAbort("gphAdd failed");
|
||||
} else {
|
||||
pgphentry->userPvt = pdbRecordType;
|
||||
}
|
||||
ellAdd(&pdbbase->recordTypeList,&pdbRecordType->node);
|
||||
ellAdd(&savedPdbbase->recordTypeList,&pdbRecordType->node);
|
||||
}
|
||||
|
||||
static void dbDevice(char *recordtype,char *linktype,
|
||||
@@ -798,7 +799,7 @@ static void dbDevice(char *recordtype,char *linktype,
|
||||
dbRecordType *pdbRecordType;
|
||||
GPHENTRY *pgphentry;
|
||||
int i,link_type;
|
||||
pgphentry = gphFind(pdbbase->pgpHash,recordtype,&pdbbase->recordTypeList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,recordtype,&savedPdbbase->recordTypeList);
|
||||
if(!pgphentry) {
|
||||
epicsPrintf("Record type \"%s\" not found for device \"%s\"\n",
|
||||
recordtype, choicestring);
|
||||
@@ -819,7 +820,7 @@ static void dbDevice(char *recordtype,char *linktype,
|
||||
return;
|
||||
}
|
||||
pdbRecordType = (dbRecordType *)pgphentry->userPvt;
|
||||
pgphentry = gphFind(pdbbase->pgpHash,choicestring,&pdbRecordType->devList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,choicestring,&pdbRecordType->devList);
|
||||
if(pgphentry) {
|
||||
return;
|
||||
}
|
||||
@@ -827,7 +828,7 @@ static void dbDevice(char *recordtype,char *linktype,
|
||||
pdevSup->name = epicsStrDup(dsetname);
|
||||
pdevSup->choice = epicsStrDup(choicestring);
|
||||
pdevSup->link_type = link_type;
|
||||
pgphentry = gphAdd(pdbbase->pgpHash,pdevSup->choice,&pdbRecordType->devList);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash,pdevSup->choice,&pdbRecordType->devList);
|
||||
if(!pgphentry) {
|
||||
yyerrorAbort("gphAdd failed");
|
||||
} else {
|
||||
@@ -845,18 +846,18 @@ static void dbDriver(char *name)
|
||||
yyerrorAbort("dbDriver: Driver name can't be empty");
|
||||
return;
|
||||
}
|
||||
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->drvList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->drvList);
|
||||
if(pgphentry) {
|
||||
return;
|
||||
}
|
||||
pdrvSup = dbCalloc(1,sizeof(drvSup));
|
||||
pdrvSup->name = epicsStrDup(name);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash,pdrvSup->name,&pdbbase->drvList);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash,pdrvSup->name,&savedPdbbase->drvList);
|
||||
if(!pgphentry) {
|
||||
yyerrorAbort("gphAdd failed");
|
||||
}
|
||||
pgphentry->userPvt = pdrvSup;
|
||||
ellAdd(&pdbbase->drvList,&pdrvSup->node);
|
||||
ellAdd(&savedPdbbase->drvList,&pdrvSup->node);
|
||||
}
|
||||
|
||||
static void dbLinkType(char *name, char *jlif_name)
|
||||
@@ -864,19 +865,19 @@ static void dbLinkType(char *name, char *jlif_name)
|
||||
linkSup *pLinkSup;
|
||||
GPHENTRY *pgphentry;
|
||||
|
||||
pgphentry = gphFind(pdbbase->pgpHash, name, &pdbbase->linkList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash, name, &savedPdbbase->linkList);
|
||||
if (pgphentry) {
|
||||
return;
|
||||
}
|
||||
pLinkSup = dbCalloc(1,sizeof(linkSup));
|
||||
pLinkSup->name = epicsStrDup(name);
|
||||
pLinkSup->jlif_name = epicsStrDup(jlif_name);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash, pLinkSup->name, &pdbbase->linkList);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash, pLinkSup->name, &savedPdbbase->linkList);
|
||||
if (!pgphentry) {
|
||||
yyerrorAbort("gphAdd failed");
|
||||
}
|
||||
pgphentry->userPvt = pLinkSup;
|
||||
ellAdd(&pdbbase->linkList, &pLinkSup->node);
|
||||
ellAdd(&savedPdbbase->linkList, &pLinkSup->node);
|
||||
}
|
||||
|
||||
static void dbRegistrar(char *name)
|
||||
@@ -888,18 +889,18 @@ static void dbRegistrar(char *name)
|
||||
yyerrorAbort("dbRegistrar: Registrar name can't be empty");
|
||||
return;
|
||||
}
|
||||
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->registrarList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->registrarList);
|
||||
if(pgphentry) {
|
||||
return;
|
||||
}
|
||||
ptext = dbCalloc(1,sizeof(dbText));
|
||||
ptext->text = epicsStrDup(name);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash,ptext->text,&pdbbase->registrarList);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash,ptext->text,&savedPdbbase->registrarList);
|
||||
if(!pgphentry) {
|
||||
yyerrorAbort("gphAdd failed");
|
||||
}
|
||||
pgphentry->userPvt = ptext;
|
||||
ellAdd(&pdbbase->registrarList,&ptext->node);
|
||||
ellAdd(&savedPdbbase->registrarList,&ptext->node);
|
||||
}
|
||||
|
||||
static void dbFunction(char *name)
|
||||
@@ -911,18 +912,18 @@ static void dbFunction(char *name)
|
||||
yyerrorAbort("dbFunction: Function name can't be empty");
|
||||
return;
|
||||
}
|
||||
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->functionList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->functionList);
|
||||
if(pgphentry) {
|
||||
return;
|
||||
}
|
||||
ptext = dbCalloc(1,sizeof(dbText));
|
||||
ptext->text = epicsStrDup(name);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash,ptext->text,&pdbbase->functionList);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash,ptext->text,&savedPdbbase->functionList);
|
||||
if(!pgphentry) {
|
||||
yyerrorAbort("gphAdd failed");
|
||||
}
|
||||
pgphentry->userPvt = ptext;
|
||||
ellAdd(&pdbbase->functionList,&ptext->node);
|
||||
ellAdd(&savedPdbbase->functionList,&ptext->node);
|
||||
}
|
||||
|
||||
static void dbVariable(char *name, char *type)
|
||||
@@ -934,19 +935,19 @@ static void dbVariable(char *name, char *type)
|
||||
yyerrorAbort("dbVariable: Variable name can't be empty");
|
||||
return;
|
||||
}
|
||||
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->variableList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->variableList);
|
||||
if(pgphentry) {
|
||||
return;
|
||||
}
|
||||
pvar = dbCalloc(1,sizeof(dbVariableDef));
|
||||
pvar->name = epicsStrDup(name);
|
||||
pvar->type = epicsStrDup(type);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash,pvar->name,&pdbbase->variableList);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash,pvar->name,&savedPdbbase->variableList);
|
||||
if(!pgphentry) {
|
||||
yyerrorAbort("gphAdd failed");
|
||||
}
|
||||
pgphentry->userPvt = pvar;
|
||||
ellAdd(&pdbbase->variableList,&pvar->node);
|
||||
ellAdd(&savedPdbbase->variableList,&pvar->node);
|
||||
}
|
||||
|
||||
static void dbBreakHead(char *name)
|
||||
@@ -958,7 +959,7 @@ static void dbBreakHead(char *name)
|
||||
yyerrorAbort("dbBreakHead: Breaktable name can't be empty");
|
||||
return;
|
||||
}
|
||||
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->bptList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->bptList);
|
||||
if(pgphentry) {
|
||||
duplicate = TRUE;
|
||||
return;
|
||||
@@ -1042,17 +1043,17 @@ static void dbBreakBody(void)
|
||||
/* Continue with last slope beyond the final point */
|
||||
paBrkInt[number-1].slope = paBrkInt[number-2].slope;
|
||||
/* Add brkTable in sorted order */
|
||||
pbrkTable = (brkTable *)ellFirst(&pdbbase->bptList);
|
||||
pbrkTable = (brkTable *)ellFirst(&savedPdbbase->bptList);
|
||||
while (pbrkTable) {
|
||||
if (strcmp(pbrkTable->name, pnewbrkTable->name) > 0) {
|
||||
ellInsert(&pdbbase->bptList, ellPrevious((ELLNODE *)pbrkTable),
|
||||
ellInsert(&savedPdbbase->bptList, ellPrevious((ELLNODE *)pbrkTable),
|
||||
(ELLNODE *)pnewbrkTable);
|
||||
break;
|
||||
}
|
||||
pbrkTable = (brkTable *)ellNext(&pbrkTable->node);
|
||||
}
|
||||
if (!pbrkTable) ellAdd(&pdbbase->bptList, &pnewbrkTable->node);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash,pnewbrkTable->name,&pdbbase->bptList);
|
||||
if (!pbrkTable) ellAdd(&savedPdbbase->bptList, &pnewbrkTable->node);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash,pnewbrkTable->name,&savedPdbbase->bptList);
|
||||
if (!pgphentry) {
|
||||
yyerrorAbort("dbBreakBody: gphAdd failed");
|
||||
return;
|
||||
@@ -1103,7 +1104,7 @@ static void dbRecordHead(char *recordType, char *name, int visible)
|
||||
if(dbRecordNameValidate(name))
|
||||
return;
|
||||
|
||||
pdbentry = dbAllocEntry(pdbbase);
|
||||
pdbentry = dbAllocEntry(savedPdbbase);
|
||||
if (ellCount(&tempList))
|
||||
yyerrorAbort("dbRecordHead: tempList not empty");
|
||||
allocTemp(pdbentry);
|
||||
@@ -1260,7 +1261,7 @@ static void dbAlias(char *name, char *alias)
|
||||
if(dbRecordNameValidate(alias))
|
||||
return;
|
||||
|
||||
dbInitEntry(pdbbase, pdbEntry);
|
||||
dbInitEntry(savedPdbbase, pdbEntry);
|
||||
if (dbFindRecord(pdbEntry, name)) {
|
||||
epicsPrintf("Alias \"%s\" refers to unknown record \"%s\"\n",
|
||||
alias, name);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
\*************************************************************************/
|
||||
|
||||
#include "iocsh.h"
|
||||
#include "errSymTbl.h"
|
||||
|
||||
#include "dbStaticIocRegister.h"
|
||||
#include "dbStaticLib.h"
|
||||
@@ -217,6 +218,40 @@ static void dbReportDeviceConfigCallFunc(const iocshArgBuf *args)
|
||||
dbReportDeviceConfig(*iocshPpdbbase,stdout);
|
||||
}
|
||||
|
||||
|
||||
static const iocshArg dbCreateAliasArg0 = { "record",iocshArgStringRecord};
|
||||
static const iocshArg dbCreateAliasArg1 = { "alias",iocshArgStringRecord};
|
||||
static const iocshArg * const dbCreateAliasArgs[] = {&argPdbbase,&dbCreateAliasArg0, &dbCreateAliasArg1};
|
||||
static const iocshFuncDef dbCreateAliasFuncDef = {
|
||||
"dbCreateAlias",
|
||||
3,
|
||||
dbCreateAliasArgs,
|
||||
"Add a new record alias.\n"
|
||||
"\n"
|
||||
"Example: dbCreateAlias pdbbase record:name new:alias\n",
|
||||
};
|
||||
static void dbCreateAliasCallFunc(const iocshArgBuf *args)
|
||||
{
|
||||
DBENTRY ent;
|
||||
long status;
|
||||
|
||||
dbInitEntry(*iocshPpdbbase, &ent);
|
||||
if(!args[1].sval || !args[2].sval) {
|
||||
status = S_dbLib_recNotFound;
|
||||
|
||||
} else {
|
||||
status = dbFindRecord(&ent, args[1].sval);
|
||||
if(!status) {
|
||||
status = dbCreateAlias(&ent, args[2].sval);
|
||||
}
|
||||
}
|
||||
dbFinishEntry(&ent);
|
||||
if(status) {
|
||||
fprintf(stderr, "Error: %ld %s\n", status, errSymMsg(status));
|
||||
iocshSetError(1);
|
||||
}
|
||||
}
|
||||
|
||||
void dbStaticIocRegister(void)
|
||||
{
|
||||
iocshRegister(&dbDumpPathFuncDef, dbDumpPathCallFunc);
|
||||
@@ -234,4 +269,5 @@ void dbStaticIocRegister(void)
|
||||
iocshRegister(&dbPvdDumpFuncDef, dbPvdDumpCallFunc);
|
||||
iocshRegister(&dbPvdTableSizeFuncDef,dbPvdTableSizeCallFunc);
|
||||
iocshRegister(&dbReportDeviceConfigFuncDef, dbReportDeviceConfigCallFunc);
|
||||
iocshRegister(&dbCreateAliasFuncDef, dbCreateAliasCallFunc);
|
||||
}
|
||||
|
||||
@@ -1652,6 +1652,7 @@ long dbCreateAlias(DBENTRY *pdbentry, const char *alias)
|
||||
dbRecordNode *pnewnode;
|
||||
DBENTRY tempEntry;
|
||||
PVDENTRY *ppvd;
|
||||
long status;
|
||||
|
||||
if (!precordType)
|
||||
return S_dbLib_recordTypeNotFound;
|
||||
@@ -1664,9 +1665,10 @@ long dbCreateAlias(DBENTRY *pdbentry, const char *alias)
|
||||
return S_dbLib_recNotFound;
|
||||
|
||||
dbInitEntry(pdbentry->pdbbase, &tempEntry);
|
||||
if (!dbFindRecord(&tempEntry, alias))
|
||||
return S_dbLib_recExists;
|
||||
status = dbFindRecord(&tempEntry, alias);
|
||||
dbFinishEntry(&tempEntry);
|
||||
if (!status)
|
||||
return S_dbLib_recExists;
|
||||
|
||||
pnewnode = dbCalloc(1, sizeof(dbRecordNode));
|
||||
pnewnode->recordname = epicsStrDup(alias);
|
||||
@@ -1676,15 +1678,16 @@ long dbCreateAlias(DBENTRY *pdbentry, const char *alias)
|
||||
precnode->flags |= DBRN_FLAGS_HASALIAS;
|
||||
ellInit(&pnewnode->infoList);
|
||||
|
||||
ellAdd(&precordType->recList, &pnewnode->node);
|
||||
precordType->no_aliases++;
|
||||
|
||||
ppvd = dbPvdAdd(pdbentry->pdbbase, precordType, pnewnode);
|
||||
if (!ppvd) {
|
||||
errMessage(-1, "dbCreateAlias: Add to PVD failed");
|
||||
free(pnewnode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ellAdd(&precordType->recList, &pnewnode->node);
|
||||
precordType->no_aliases++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2041,13 +2044,17 @@ char *dbGetStringNum(DBENTRY *pdbentry)
|
||||
{
|
||||
dbFldDes *pflddes = pdbentry->pflddes;
|
||||
void *pfield = pdbentry->pfield;
|
||||
char *message;
|
||||
char *message = getpMessage(pdbentry);
|
||||
unsigned char cvttype;
|
||||
|
||||
if (!pfield) {
|
||||
dbMsgCpy(pdbentry, "Field not found");
|
||||
return message;
|
||||
}
|
||||
|
||||
/* the following assumes that messagesize is large enough
|
||||
* to hold the base 10 encoded value of a 32-bit integer.
|
||||
*/
|
||||
message = getpMessage(pdbentry);
|
||||
cvttype = pflddes->base;
|
||||
switch (pflddes->field_type) {
|
||||
case DBF_CHAR:
|
||||
@@ -2109,37 +2116,34 @@ char *dbGetStringNum(DBENTRY *pdbentry)
|
||||
{
|
||||
dbMenu *pdbMenu = (dbMenu *)pflddes->ftPvt;
|
||||
epicsEnum16 choice_ind;
|
||||
char *pchoice;
|
||||
|
||||
if (!pfield) {
|
||||
dbMsgCpy(pdbentry, "Field not found");
|
||||
return message;
|
||||
}
|
||||
choice_ind = *((epicsEnum16 *) pdbentry->pfield);
|
||||
if (!pdbMenu || choice_ind < 0 || choice_ind >= pdbMenu->nChoice)
|
||||
if (!pdbMenu)
|
||||
return NULL;
|
||||
pchoice = pdbMenu->papChoiceValue[choice_ind];
|
||||
dbMsgCpy(pdbentry, pchoice);
|
||||
|
||||
choice_ind = *((epicsEnum16 *) pdbentry->pfield);
|
||||
if (choice_ind >= pdbMenu->nChoice) {
|
||||
dbMsgPrint(pdbentry, "%u", choice_ind);
|
||||
}
|
||||
else {
|
||||
dbMsgCpy(pdbentry, pdbMenu->papChoiceValue[choice_ind]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DBF_DEVICE:
|
||||
{
|
||||
dbDeviceMenu *pdbDeviceMenu;
|
||||
dbDeviceMenu *pdbDeviceMenu = dbGetDeviceMenu(pdbentry);
|
||||
epicsEnum16 choice_ind;
|
||||
char *pchoice;
|
||||
|
||||
if (!pfield) {
|
||||
dbMsgCpy(pdbentry, "Field not found");
|
||||
return message;
|
||||
if (!pdbDeviceMenu) {
|
||||
dbMsgCpy(pdbentry, "");
|
||||
break;
|
||||
}
|
||||
pdbDeviceMenu = dbGetDeviceMenu(pdbentry);
|
||||
if (!pdbDeviceMenu)
|
||||
return NULL;
|
||||
|
||||
choice_ind = *((epicsEnum16 *) pdbentry->pfield);
|
||||
if (choice_ind<0 || choice_ind>=pdbDeviceMenu->nChoice)
|
||||
if (choice_ind>=pdbDeviceMenu->nChoice)
|
||||
return NULL;
|
||||
pchoice = pdbDeviceMenu->papChoice[choice_ind];
|
||||
dbMsgCpy(pdbentry, pchoice);
|
||||
|
||||
dbMsgCpy(pdbentry, pdbDeviceMenu->papChoice[choice_ind]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -39,7 +39,9 @@ char *dbRecordName(DBENTRY *pdbentry);
|
||||
char *dbGetStringNum(DBENTRY *pdbentry);
|
||||
long dbPutStringNum(DBENTRY *pdbentry,const char *pstring);
|
||||
|
||||
void dbMsgPrint(DBENTRY *pdbentry, const char *fmt, ...) EPICS_PRINTF_STYLE(2,3);
|
||||
void dbMsgPrint(
|
||||
DBENTRY *pdbentry, EPICS_PRINTF_FMT(const char *fmt), ...
|
||||
) EPICS_PRINTF_STYLE(2,3);
|
||||
|
||||
void dbPutStringSuggest(DBENTRY *pdbentry, const char *pstring);
|
||||
|
||||
@@ -115,6 +117,9 @@ PVDENTRY *dbPvdAdd(DBBASE *pdbbase,dbRecordType *precordType,dbRecordNode *precn
|
||||
void dbPvdDelete(DBBASE *pdbbase,dbRecordNode *precnode);
|
||||
void dbPvdFreeMem(DBBASE *pdbbase);
|
||||
|
||||
DBCORE_API
|
||||
char** dbCompleteRecord(const char *word);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
|
||||
/* dbLoadTemplate */
|
||||
static const iocshArg dbLoadTemplateArg0 = {"filename", iocshArgString};
|
||||
static const iocshArg dbLoadTemplateArg0 = {"filename", iocshArgStringPath};
|
||||
static const iocshArg dbLoadTemplateArg1 = {"var1=value1,var2=value2", iocshArgString};
|
||||
static const iocshArg * const dbLoadTemplateArgs[2] = {
|
||||
&dbLoadTemplateArg0, &dbLoadTemplateArg1
|
||||
|
||||
@@ -17,7 +17,7 @@ IOCSH_STATIC_FUNC void dlload(const char* name)
|
||||
}
|
||||
}
|
||||
|
||||
static const iocshArg dlloadArg0 = { "path/library.so", iocshArgString};
|
||||
static const iocshArg dlloadArg0 = { "path/library.so", iocshArgStringPath};
|
||||
static const iocshArg * const dlloadArgs[] = {&dlloadArg0};
|
||||
static const iocshFuncDef dlloadFuncDef = {
|
||||
"dlload",
|
||||
|
||||
@@ -716,13 +716,13 @@ int iocShutdown(void)
|
||||
iterateRecords(doCloseLinks, NULL);
|
||||
initHookAnnounce(initHookAfterCloseLinks);
|
||||
|
||||
if (iocBuildMode == buildIsolated) {
|
||||
/* stop and "join" threads */
|
||||
scanStop();
|
||||
initHookAnnounce(initHookAfterStopScan);
|
||||
callbackStop();
|
||||
initHookAnnounce(initHookAfterStopCallback);
|
||||
} else {
|
||||
/* stop and "join" threads */
|
||||
scanStop();
|
||||
initHookAnnounce(initHookAfterStopScan);
|
||||
callbackStop();
|
||||
initHookAnnounce(initHookAfterStopCallback);
|
||||
|
||||
if (iocBuildMode != buildIsolated) {
|
||||
dbStopServers();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,59 +1,214 @@
|
||||
=head1 Channel Filters
|
||||
=title Field Modifiers and Channel Filters
|
||||
|
||||
Channel Filters can be applied to Channel Access channels by a client, using
|
||||
a JSON Field Modifier to select the filter and any parameters.
|
||||
The following filters are available in this release:
|
||||
=head2 Contents
|
||||
|
||||
=over
|
||||
|
||||
=item * L<TimeStamp|/"TimeStamp Filter ts">
|
||||
=item * L<Introduction
|
||||
|/"Introduction">
|
||||
|
||||
=item * L<Deadband|/"Deadband Filter dbnd">
|
||||
=item * L<Using Field Modifiers and Channel Filters
|
||||
|/"Using Field Modifiers and Channel Filters">
|
||||
|
||||
=item * L<Array|/"Array Filter arr">
|
||||
=item * L<Example Filters
|
||||
|/"Example Filters">
|
||||
|
||||
=item * L<Synchronize|/"Synchronize Filter sync">
|
||||
=item * L<Field Modifier Reference
|
||||
|/"Field Modifier Reference">
|
||||
|
||||
=item * L<Decimation|/"Decimation Filter dec">
|
||||
=over
|
||||
|
||||
=item * L<UTag|/"UTag Filter utag">
|
||||
=item * L<Long String Modifier C<$>
|
||||
|/"Long String Field Modifier $">
|
||||
|
||||
=item * L<Subarray Modifier C<<< [E<hellip>] >>>
|
||||
|/"Subarray Field Modifier [start:increment:end]">
|
||||
|
||||
=back
|
||||
|
||||
=head2 Using Filters
|
||||
=item * L<JSON5 Channel Filters
|
||||
|/"JSON5 Channel Filters">
|
||||
|
||||
Channel filters can be added to any Channel Access channel name.
|
||||
There can be more than one filter applied to the same channel, in which case the
|
||||
order that they are specified will control the order in which they are applied
|
||||
to the resulting data-stream.
|
||||
The filter specification must appear after the field name, or if the default
|
||||
(VAL) field is used after a dot C<.> appended to the record name.
|
||||
With the exception of the array short-hand which is described below, all filters
|
||||
must appear inside a pair of braces C< {} > after the dot expressed as a JSON
|
||||
(L<JavaScript Object Notation|http://www.json.org/>) object, which allows filter
|
||||
parameters to be included as needed.
|
||||
=over
|
||||
|
||||
Each filter is given as a name/value pair. The filter name (given in parentheses
|
||||
in the titles below) is a string, and must be enclosed inside double-quotes C<">
|
||||
characters as per the JSON specification.
|
||||
Parameters to that filter are provided as the value part of the name/value pair,
|
||||
and will normally appear as a child JSON object consisting of name/value pairs
|
||||
inside a nested pair of braces C< {} >.
|
||||
=item * L<TimeStamp Filter C<<< {ts:{}} >>>
|
||||
|/"TimeStamp Filter ts">
|
||||
|
||||
=head4 Example Filter
|
||||
=item * L<Deadband Filter C<<< {dbnd:{E<hellip>}} >>>
|
||||
|/"Deadband Filter dbnd">
|
||||
|
||||
Given a record called C<test:channel> the following would apply a filter C<f> to
|
||||
the VAL field of that record, giving the filter two numeric parameters named
|
||||
C<lo> and C<hi>:
|
||||
=item * L<Array Filter C<<< {arr:{E<hellip>}} >>>
|
||||
|/"Array Filter arr">
|
||||
|
||||
test:channel.{"f":{"lo":0,"hi":10}}
|
||||
=item * L<Synchronize Filter C<<< {sync:{E<hellip>}} >>>
|
||||
|/"Synchronize Filter sync">
|
||||
|
||||
Note that due to the required presence of the double-quote characters in the
|
||||
JSON strings in the name string, it will usually be necessary to enclose a
|
||||
filtered name within single-quotes C<< ' ... ' >> when typing it as an
|
||||
argument to a Unix shell command.
|
||||
=item * L<Decimation Filter C<<< {dec:{E<hellip>}} >>>
|
||||
|/"Decimation Filter dec">
|
||||
|
||||
=head2 Filter Reference
|
||||
=item * L<User Tag Filter C<<< {utag:{E<hellip>}} >>>
|
||||
|/"User Tag Filter utag">
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head2 Introduction
|
||||
|
||||
A Field Modifier is a string that is appended to the field name part of a
|
||||
Channel Access or PV Access channel name of an IOC-based server.
|
||||
The IOC currently recognizes 3 different kinds of field modifier, which are
|
||||
described below.
|
||||
|
||||
A Channel Filter is an IOC plugin that can be attached to an IOC process
|
||||
variable channel using a field modifier, and can alter the data updates that are
|
||||
served to the client that requested the filtering.
|
||||
Clients that use a channel filter have no effect on other clients connected to
|
||||
the same PV.
|
||||
Filters can only modify or drop monitor events that the IOC posts; introducing
|
||||
extra monitor events (in between the events posted by the IOC itself) is not
|
||||
currently possible.
|
||||
|
||||
Most Channel Filters are configured by a field modifier that uses JSON5
|
||||
syntax to select the type of filter and provide any parameters it accepts.
|
||||
|
||||
|
||||
=head2 Using Field Modifiers and Channel Filters
|
||||
|
||||
Modifiers can be added to any Channel Access or PV Access channel name.
|
||||
There can be more than one modifier or filter applied to a channel.
|
||||
Modifiers must appear immediately after the field name if one is included in the
|
||||
channel name.
|
||||
If no field is named because the default (VAL) field is the target, the
|
||||
modifiers must come immediately after a dot C<.> appended to the record name.
|
||||
|
||||
The order that modifiers and filters are specified controls the order in which
|
||||
they are applied to the resulting data-stream.
|
||||
If used, the Long String and Subarray field modifiers must appear first and in
|
||||
that order, followed by any channel filters inside a pair of braces C< {} > that
|
||||
form a JSON5 (L<JavaScript Object Notation|https://spec.json5.org/>) map, which
|
||||
allows filters and their parameters to be specified as needed.
|
||||
|
||||
Each JSON5 filter is specified as a name:value pair.
|
||||
The filter name is a map key that may be an unquoted identifier name (which all
|
||||
of the filter names given below are), or a string enclosed inside either single-
|
||||
or double-quote characters C<'> or C<"> as per the JSON5 specification.
|
||||
Parameters to the filter are provided in the value part of the key:value pair
|
||||
after the colon C<:> character, and will normally be an embedded JSON5 map
|
||||
containing zero or more key/value pairs inside a nested pair of braces C< {} >.
|
||||
|
||||
Unless included inside a quoted string, white space characters are ignored and
|
||||
skipped over by the JSON5 parser between other token characters.
|
||||
This includes horizontal and vertical tabs, line feed/form feed/carriage return,
|
||||
space and the non-breaking space character C< \xA0 >.
|
||||
Within a quoted string, line breaks may escaped with a backslash C<\> to be
|
||||
omitted from the parsed string.
|
||||
|
||||
An IOC Channel Access link can include filters in its channel name, but it is
|
||||
important to not include any spaces at all in the filter specification.
|
||||
If a filter name or parameter must contain a space it will be necessary to
|
||||
express that space character as an escaped character C<\x20> or C<\u0020> inside
|
||||
a quoted string, otherwise the space will mark the end of the channel name to
|
||||
the link parsing code inside the IOC.
|
||||
|
||||
=head4 Example Filters
|
||||
|
||||
Given a record called C<test:channel> the following would all apply a channel
|
||||
filter C<f> to the VAL field of that record, giving the filter two numeric
|
||||
parameters named C<lo> and C<hi>:
|
||||
|
||||
test:channel.{f:{lo:0,hi:10}}
|
||||
test:channel.{"f":{"lo":0, "hi":10}}
|
||||
test:channel.{'f': {'lo':0, 'hi':10} }
|
||||
|
||||
When typing a filtered channel name as an argument to a Unix shell command, if
|
||||
quote characters are used for keys or values in JSON strings within the channel
|
||||
name string it may be necessary to enclose the name within quotes C<'> or C<">
|
||||
or to use back-slash escapes before them.
|
||||
Quotes may not be required when the Long String modifier C<$> is used at the end
|
||||
of a field name with nothing following it, but will be necessary for a square
|
||||
bracketted Subarray filter or if a dollar sign is followed by something else.
|
||||
|
||||
Hal$ caget test:channel.{f:{lo:0,hi:10}}
|
||||
...
|
||||
Hal$ caget 'test:channel.{"f":{"lo":0, "hi":10}}'
|
||||
...
|
||||
Hal$ caget -S calc:record.CALC$
|
||||
...
|
||||
Hal$ caget -S 'test:channel.NAME$[0:4]'
|
||||
test:channel.NAME$[0:4] test
|
||||
|
||||
=head2 Field Modifier Reference
|
||||
|
||||
The two built-in field modifiers use a simplified syntax following the record
|
||||
field name.
|
||||
|
||||
=head3 Long String Field Modifier C<$>
|
||||
|
||||
Appending a dollar sign C<$> to the name of a C<DBF_STRING> field causes the IOC
|
||||
to change the representation of that field into an array of characters, which
|
||||
allows strings longer than 40 character to be transported through Channel
|
||||
Access.
|
||||
Long strings are particularly useful for the CALC fields of a calculation or
|
||||
calcout record, which can hold up to 80 characters, or the VAL fields of an lsi
|
||||
(Long String Input) or lso (Long String Output) record which can be any length
|
||||
as chosen by the database designer.
|
||||
|
||||
Hal$ cainfo test:channel.NAME
|
||||
test:channel.NAME
|
||||
State: connected
|
||||
Host: 10.234.56.78:5064
|
||||
Access: read, no write
|
||||
Native data type: DBF_STRING
|
||||
Request type: DBR_STRING
|
||||
Element count: 1
|
||||
Hal$ cainfo test:channel.NAME$
|
||||
test:channel.NAME$
|
||||
State: connected
|
||||
Host: 10.234.56.78:5064
|
||||
Access: read, no write
|
||||
Native data type: DBF_CHAR
|
||||
Request type: DBR_CHAR
|
||||
Element count: 61
|
||||
|
||||
A CA client accessing a channel that uses the Long String field modifier will
|
||||
have to be specifically configured to treat the data as a string instead of the
|
||||
array of C<DBF_CHAR> that it looks like.
|
||||
CA clients should not attempt to parse the channel name themselves to recognize
|
||||
this field modifier in the name.
|
||||
All long string values returned by the IOC should include a trailing zero byte
|
||||
in their data as is standard for strings in the C language.
|
||||
For the catools programs provided with Base, the flag C<-S> indicates that a
|
||||
channel containing a character array should be treated as a long string.
|
||||
|
||||
Hal$ caget test:channel.NAME
|
||||
test:channel.NAME test:channel
|
||||
Hal$ caget test:channel.NAME$
|
||||
test:channel.NAME$ 61 116 101 115 116 58 99 104 97 110 110 101 108 0 0 ...
|
||||
Hal$ caget -S test:channel.NAME$
|
||||
test:channel.NAME$ test:channel
|
||||
|
||||
|
||||
=head3 Subarray Field Modifier C<[start:increment:end]>
|
||||
|
||||
This square-bracket field modifier syntax gets translated within the IOC into
|
||||
calls to the L<Array Filter|/"Array Filter arr">, see that section below for
|
||||
details of this shorthand.
|
||||
|
||||
The subarray field modifier syntax can immediately follow a Long String field
|
||||
modifier, which permits fetching various kinds of substrings from the field.
|
||||
This syntax cannot appear after a JSON filter specification though, the JSON
|
||||
"arr" filter syntax must be used to apply an array filter after any other JSON
|
||||
filter type.
|
||||
|
||||
Hal$ caget -S 'test:channel.NAME$[0:4]'
|
||||
test:channel.NAME$[0:4] test
|
||||
Hal$ caget -S 'test:channel.NAME$[5:-1]'
|
||||
test:channel.NAME$[5:-1] channel
|
||||
|
||||
|
||||
=head2 JSON5 Channel Filters
|
||||
|
||||
=cut
|
||||
|
||||
@@ -61,22 +216,96 @@ registrar(tsInitialize)
|
||||
|
||||
=head3 TimeStamp Filter C<"ts">
|
||||
|
||||
This filter is used to set the timestamp of the value fetched through
|
||||
the channel to the time the value was fetched (or an update was sent),
|
||||
rather than the time the record last
|
||||
processed which could have been days or even weeks ago for some records, or set
|
||||
to the EPICS epoch if the record has never processed.
|
||||
This filter is used for two purposes:
|
||||
|
||||
=over
|
||||
|
||||
=item * to retrieve the timestamp of the record as a value in several different
|
||||
formats;
|
||||
|
||||
=item * to retrieve the record value as normal, but replace the timestamp with
|
||||
the time the value was fetched.
|
||||
|
||||
=back
|
||||
|
||||
=head4 Parameters
|
||||
|
||||
None, use an empty pair of braces.
|
||||
=head4 No parameters (an empty pair of braces)
|
||||
|
||||
=head4 Example
|
||||
Retrieve the record value as normal, but replace the timestamp with the time the
|
||||
value was fetched (or an update was sent). This is useful for clients that can't
|
||||
handle timestamps that are far in the past. Normally, the record's timestamp
|
||||
indicates when the record last processed, which could have been days or even
|
||||
weeks ago for some records, or set to the EPICS epoch if the record has never
|
||||
processed.
|
||||
|
||||
Hal$ caget -a 'test:channel.{"ts":{}}'
|
||||
test:channel.{"ts":{}} 2012-08-28 22:10:31.192547 0 UDF INVALID
|
||||
Hal$ caget -a 'test:channel'
|
||||
test:channel <undefined> 0 UDF INVALID
|
||||
=head4 Numeric type C<"num">
|
||||
|
||||
The following values are accepted for this parameter:
|
||||
|
||||
=over
|
||||
|
||||
=item * C<"dbl"> requests the timestamp as C<epicsFloat64> representing the
|
||||
non-integral number of seconds since epoch. This format is convenient,
|
||||
but loses precision, depending on which epoch is used.
|
||||
|
||||
=item * C<"sec"> requests the number of seconds since epoch as C<epicsUInt32>.
|
||||
|
||||
=item * C<"nsec"> requests the number of nanoseconds since epoch as
|
||||
C<epicsUInt32>.
|
||||
|
||||
=item * C<"ts"> requests the entire timestamp. It is provided as a two-element
|
||||
array of C<epicsUInt32> representing seconds and nanoseconds.
|
||||
|
||||
=back
|
||||
|
||||
Note that C<epicsUInt32> cannot be transferred over Channel Access; in that
|
||||
case, the value will be converted to C<epicsFloat64>.
|
||||
|
||||
=head4 String type C<"str">
|
||||
|
||||
The following values are accepted for this parameter:
|
||||
|
||||
=over
|
||||
|
||||
=item * C<"epics"> requests the timestamp as a string in the format used by
|
||||
tools such as C<caget>.
|
||||
|
||||
=item * C<"iso"> requests the timestamp as a string in the ISO8601 format.
|
||||
|
||||
=back
|
||||
|
||||
=head4 Epoch adjustment C<"epoch">
|
||||
|
||||
The following values are accepted for this parameter:
|
||||
|
||||
=over
|
||||
|
||||
=item * C<"epics"> keeps the EPICS epoch (1990-01-01) and is the default if the
|
||||
C<"epoch"> parameter is not specified.
|
||||
|
||||
=item * C<"unix"> converts the timestamp to the UNIX/POSIX epoch (1970-01-01).
|
||||
|
||||
=back
|
||||
|
||||
=head4 Examples
|
||||
|
||||
Hal$ caget -a 'test:invalid_ts.{"ts":{}}'
|
||||
test:invalid_ts.{"ts":{}} 2012-08-28 22:10:31.192547 0 UDF INVALID
|
||||
Hal$ caget -a 'test:invalid_ts'
|
||||
test:invalid_ts <undefined> 0 UDF INVALID
|
||||
Hal$ caget -a test:channel
|
||||
test:channel 2021-03-11 18:23:48.265386 42
|
||||
Hal$ caget 'test:channel.{"ts": {"str": "epics"}}'
|
||||
test:channel.{"ts": {"str": "epics"}} 2021-03-11 18:23:48.265386
|
||||
Hal$ caget 'test:channel.{"ts": {"str": "iso"}}'
|
||||
test:channel.{"ts": {"str": "iso"}} 2021-03-11T18:23:48.265386+0100
|
||||
Hal$ caget -f9 'test:channel.{"ts": {"num": "dbl"}}'
|
||||
test:channel.{"ts": {"num": "dbl"}} 984331428.265386105
|
||||
Hal$ caget -f1 'test:channel.{"ts": {"num": "ts"}}'
|
||||
test:channel.{"ts": {"num": "ts"}} 2 984331428.0 265386163.0
|
||||
Hal$ caget -f1 'test:channel.{"ts": {"num": "ts", "epoch": "unix"}}'
|
||||
test:channel.{"ts": {"num": "ts", "epoch": "unix"}} 2 1615483428.0 265386163.0
|
||||
|
||||
=cut
|
||||
|
||||
@@ -125,7 +354,7 @@ The default mode is C<abs> if no mode parameter is included.
|
||||
test:channel 2012-09-01 22:10:23.601023 5
|
||||
test:channel 2012-09-01 22:10:24.601136 6 HIGH MINOR
|
||||
^C
|
||||
Hal$ camonitor 'test:channel.{"dbnd":{"abs":1.5}}'
|
||||
Hal$ camonitor 'test:channel.{"dbnd":{"d":1.5}}'
|
||||
test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:49.613341 1 LOLO MAJOR
|
||||
test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:51.613615 3 LOW MINOR
|
||||
test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:53.613804 5
|
||||
@@ -144,16 +373,21 @@ subarrays).
|
||||
|
||||
=head4 Parameters
|
||||
|
||||
Note: Negative index numbers address from the end of the array, with C<-1> being the last element.
|
||||
|
||||
=over
|
||||
|
||||
=item Square bracket notation C<[start:increment:end]> (shorthand)
|
||||
|
||||
The common square bracket notation which can be used in place of JSON.
|
||||
This much shorter square bracket notation can be used in place of JSON.
|
||||
Any parameter may be omitted (keeping the colons) to use the default value.
|
||||
If only one colon is included, this means C<[start:end]> with an increment of 1.
|
||||
If only a single parameter is used C<[index]> the filter returns one element.
|
||||
If only one colon is included, it means C<[start:end]> with an increment of 1.
|
||||
If only a single parameter is given C<[index]> the filter returns one element.
|
||||
|
||||
Index numbers for the start and end parameters must be integers, with the first
|
||||
array element being found at index C<0>.
|
||||
The value of an index may be negative, in which case the indexing is counted
|
||||
backwards from the end of the array, with C<-1> being the last element.
|
||||
If the start index selects an element that comes after the end index element,
|
||||
the subarray returned will always be empty.
|
||||
|
||||
=item Start index C<"s">
|
||||
|
||||
@@ -161,8 +395,8 @@ Index of the first original array element to retrieve.
|
||||
|
||||
=item Increment C<"i">
|
||||
|
||||
Index increment between retrieved elements of the original array; must be
|
||||
a positive number.
|
||||
The stride or increment to apply between elements of the original array to be
|
||||
retrieved. This value must be a positive integer.
|
||||
|
||||
=item End index C<"e">
|
||||
|
||||
@@ -176,9 +410,9 @@ C<s=0> (first element), C<i=1> (fetch all elements), C<e=-1>
|
||||
|
||||
=head4 Example
|
||||
|
||||
Hal$ caget test:channel 'test:channel.{"arr":{"s":2,"i":2,"e":8}}' test:channel.[3:5] test:channel.[3:2:-3]
|
||||
Hal$ caget test:channel 'test:channel.{"arr":{s:2,i:2,e:8}}' test:channel.[3:5] test:channel.[3:2:-3]
|
||||
test:channel 10 0 1 2 3 4 5 6 7 8 9
|
||||
test:channel.{"arr":{"s":2,"i":2,"e":8}} 4 2 4 6 8
|
||||
test:channel.{"arr":{s:2,i:2,e:8}} 4 2 4 6 8
|
||||
test:channel.[3:5] 3 3 4 5
|
||||
test:channel.[3:2:-3] 3 3 5 7
|
||||
|
||||
@@ -202,13 +436,14 @@ C<dbStateSet()>.
|
||||
=item Mode+State
|
||||
|
||||
Mode and state can be specified in one definition (shorthand).
|
||||
The desired mode is given as parameter name (C<"before"> / C<"first"> /
|
||||
C<"while"> / C<"last"> / C<"after"> / C<"unless">), with the state name
|
||||
(enclosed in double quotes C<">) as value.
|
||||
The desired mode is given as the parameter name (C<"before"> / C<"first"> /
|
||||
C<"while"> / C<"last"> / C<"after"> / C<"unless"> which may be unquoted), with
|
||||
the state name (enclosed in single or double quotes C<"> or C<'>) as the value.
|
||||
|
||||
=item Mode C<"m">
|
||||
|
||||
A single word from the list below, enclosed in double quotes C<">.
|
||||
A single word from the list below, enclosed in single or double quotes C<'> or
|
||||
C<">.
|
||||
This controls how the state value should affect the monitor stream.
|
||||
|
||||
=over
|
||||
@@ -235,7 +470,7 @@ as the state is false.
|
||||
|
||||
=item State C<"s">
|
||||
|
||||
The name of a state variable, enclosed in double quotes C<">.
|
||||
The name of a state variable, enclosed in single or double quotes C<"> or C<'>.
|
||||
|
||||
=back
|
||||
|
||||
@@ -245,7 +480,7 @@ Assuming there is a system state called "blue", that is being controlled by
|
||||
some other facility such as a timing system, updates could be restricted to
|
||||
periods only when "blue" is true by using
|
||||
|
||||
Hal$ camonitor 'test:channel' 'test:channel.{"sync":{"while":"blue"}}'
|
||||
Hal$ camonitor 'test:channel' 'test:channel.{sync:{while:"blue"}}'
|
||||
...
|
||||
|
||||
=cut
|
||||
@@ -283,17 +518,18 @@ client connects.
|
||||
To sample a 60Hz channel at 1Hz, a 10Hz channel every 6 seconds or a 1Hz channel
|
||||
once every minute:
|
||||
|
||||
Hal$ camonitor 'test:channel' 'test:channel.{"dec":{"n":60}}'
|
||||
Hal$ camonitor 'test:channel' 'test:channel.{dec:{n:60}}'
|
||||
...
|
||||
|
||||
=cut
|
||||
|
||||
registrar(utagInitialize)
|
||||
|
||||
=head3 UTag Filter C<"utag">
|
||||
=head3 User Tag Filter C<"utag">
|
||||
|
||||
This filter applies a test UTAG&M==V to the value taken from the UTAG record field
|
||||
and drops those updates which evaluate as false.
|
||||
This filter applies a test C< (UTAG & M) == V > to the value of the record's
|
||||
UTAG field at the time each monitor event is posted, and drops all updates for
|
||||
which this expression is false.
|
||||
|
||||
=head4 Parameters
|
||||
|
||||
@@ -301,12 +537,19 @@ and drops those updates which evaluate as false.
|
||||
|
||||
=item Mask C<"M">
|
||||
|
||||
Bit mask.
|
||||
An integer to be used as a bit mask.
|
||||
|
||||
=item Value C<"V">
|
||||
|
||||
Required value.
|
||||
The integer value to be matched after applying the mask to the UTAG value.
|
||||
|
||||
=back
|
||||
|
||||
=head4 Example
|
||||
|
||||
To read a channel only when the UTAG value is even (bit 0 is 0):
|
||||
|
||||
Hal$ camonitor 'test:channel' 'test:channel.{utag:{m:1, v:0}}'
|
||||
...
|
||||
|
||||
=cut
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2021 Cosylab d.d
|
||||
* Copyright (c) 2010 Brookhaven National Laboratory.
|
||||
* Copyright (c) 2010 Helmholtz-Zentrum Berlin
|
||||
* fuer Materialien und Energie GmbH.
|
||||
@@ -9,6 +10,7 @@
|
||||
|
||||
/*
|
||||
* Author: Ralph Lange <Ralph.Lange@bessy.de>
|
||||
* Author: Jure Varlec <jure.varlec@cosylab.com>
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -16,20 +18,124 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "chfPlugin.h"
|
||||
#include "db_field_log.h"
|
||||
#include "dbExtractArray.h"
|
||||
#include "dbLock.h"
|
||||
#include "db_field_log.h"
|
||||
#include "epicsExport.h"
|
||||
#include "freeList.h"
|
||||
#include "errlog.h"
|
||||
|
||||
/*
|
||||
* The size of the data is different for each channel, and can even
|
||||
* change at runtime, so a freeList doesn't make much sense here.
|
||||
*/
|
||||
/* Allocation size for freelists */
|
||||
#define ALLOC_NUM_ELEMENTS 32
|
||||
|
||||
#define logicErrorMessage() \
|
||||
errMessage(-1, "Logic error: invalid state encountered in ts filter")
|
||||
|
||||
/* Filter settings */
|
||||
|
||||
enum tsMode {
|
||||
tsModeInvalid = 0,
|
||||
tsModeGenerate = 1,
|
||||
tsModeDouble = 2,
|
||||
tsModeSec = 3,
|
||||
tsModeNsec = 4,
|
||||
tsModeArray = 5,
|
||||
tsModeString = 6,
|
||||
};
|
||||
|
||||
static const chfPluginEnumType ts_numeric_enum[] = {
|
||||
{"dbl", 2}, {"sec", 3}, {"nsec", 4}, {"ts", 5}};
|
||||
|
||||
enum tsEpoch {
|
||||
tsEpochEpics = 0,
|
||||
tsEpochUnix = 1,
|
||||
};
|
||||
|
||||
static const chfPluginEnumType ts_epoch_enum[] = {{"epics", 0}, {"unix", 1}};
|
||||
|
||||
enum tsString {
|
||||
tsStringInvalid = 0,
|
||||
tsStringEpics = 1,
|
||||
tsStringIso = 2,
|
||||
};
|
||||
|
||||
static const chfPluginEnumType ts_string_enum[] = {{"epics", 1}, {"iso", 2}};
|
||||
|
||||
typedef struct tsPrivate {
|
||||
enum tsMode mode;
|
||||
enum tsEpoch epoch;
|
||||
enum tsString str;
|
||||
} tsPrivate;
|
||||
|
||||
static const chfPluginArgDef ts_args[] = {
|
||||
chfEnum(tsPrivate, mode, "num", 0, 0, ts_numeric_enum),
|
||||
chfEnum(tsPrivate, epoch, "epoch", 0, 0, ts_epoch_enum),
|
||||
chfEnum(tsPrivate, str, "str", 0, 0, ts_string_enum),
|
||||
chfPluginArgEnd
|
||||
};
|
||||
|
||||
static int parse_finished(void *pvt) {
|
||||
tsPrivate *settings = (tsPrivate *)pvt;
|
||||
if (settings->str != tsStringInvalid) {
|
||||
settings->mode = tsModeString;
|
||||
#if defined _MSC_VER && _MSC_VER <= 1700
|
||||
// VS 2012 crashes in ISO mode, doesn't support timezones
|
||||
if (settings->str == tsStringIso) {
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
} else if (settings->mode == tsModeInvalid) {
|
||||
settings->mode = tsModeGenerate;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Allocation of filter settings */
|
||||
static void *private_free_list;
|
||||
|
||||
static void * allocPvt() {
|
||||
return freeListCalloc(private_free_list);
|
||||
}
|
||||
|
||||
static void freePvt(void *pvt) {
|
||||
freeListFree(private_free_list, pvt);
|
||||
}
|
||||
|
||||
|
||||
/* Allocation of two-element arrays for second+nanosecond pairs */
|
||||
static void *ts_array_free_list;
|
||||
|
||||
static void *allocTsArray() {
|
||||
return freeListCalloc(ts_array_free_list);
|
||||
}
|
||||
|
||||
static void freeTsArray(db_field_log *pfl) {
|
||||
freeListFree(ts_array_free_list, pfl->u.r.field);
|
||||
}
|
||||
|
||||
/* Allocation of strings */
|
||||
static void *string_free_list;
|
||||
|
||||
static void *allocString() {
|
||||
return freeListCalloc(string_free_list);
|
||||
}
|
||||
|
||||
static void freeString(db_field_log *pfl) {
|
||||
freeListFree(string_free_list, pfl->u.r.field);
|
||||
}
|
||||
|
||||
|
||||
/* The dtor for waveform data for the case when we have to copy it. */
|
||||
static void freeArray(db_field_log *pfl) {
|
||||
/*
|
||||
* The size of the data is different for each channel, and can even
|
||||
* change at runtime, so a freeList doesn't make much sense here.
|
||||
*/
|
||||
free(pfl->u.r.field);
|
||||
}
|
||||
|
||||
static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
|
||||
static db_field_log* generate(void* pvt, dbChannel *chan, db_field_log *pfl) {
|
||||
epicsTimeStamp now;
|
||||
epicsTimeGetCurrent(&now);
|
||||
|
||||
@@ -44,7 +150,7 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
|
||||
dbScanLock(dbChannelRecord(chan));
|
||||
dbChannelGetArrayInfo(chan, &pSource, &nSource, &offset);
|
||||
dbExtractArray(pSource, pTarget, pfl->field_size,
|
||||
nSource, pfl->no_elements, offset, 1);
|
||||
nSource, pfl->no_elements, offset, 1);
|
||||
pfl->u.r.field = pTarget;
|
||||
pfl->dtor = freeArray;
|
||||
pfl->u.r.pvt = pvt;
|
||||
@@ -56,34 +162,246 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
|
||||
return pfl;
|
||||
}
|
||||
|
||||
static void channelRegisterPre(dbChannel *chan, void *pvt,
|
||||
chPostEventFunc **cb_out, void **arg_out, db_field_log *probe)
|
||||
{
|
||||
static db_field_log *replace_fl_value(tsPrivate const *pvt,
|
||||
db_field_log *pfl,
|
||||
int (*func)(tsPrivate const *,
|
||||
db_field_log *)) {
|
||||
/* Get rid of the old value */
|
||||
if (pfl->type == dbfl_type_ref && pfl->dtor) {
|
||||
pfl->dtor(pfl);
|
||||
pfl->dtor = NULL;
|
||||
}
|
||||
pfl->no_elements = 1;
|
||||
pfl->type = dbfl_type_val;
|
||||
|
||||
if (func(pvt, pfl)) {
|
||||
db_delete_field_log(pfl);
|
||||
pfl = NULL;
|
||||
}
|
||||
|
||||
return pfl;
|
||||
}
|
||||
|
||||
static void ts_to_array(tsPrivate const *settings,
|
||||
epicsTimeStamp const *ts,
|
||||
epicsUInt32 arr[2]) {
|
||||
arr[0] = ts->secPastEpoch;
|
||||
arr[1] = ts->nsec;
|
||||
if (settings->epoch == tsEpochUnix) {
|
||||
/* Cannot use epicsTimeToWhatever because Whatever uses signed ints */
|
||||
arr[0] += POSIX_TIME_AT_EPICS_EPOCH;
|
||||
}
|
||||
}
|
||||
|
||||
static int ts_seconds(tsPrivate const *settings, db_field_log *pfl) {
|
||||
epicsUInt32 arr[2];
|
||||
ts_to_array(settings, &pfl->time, arr);
|
||||
pfl->field_type = DBF_ULONG;
|
||||
pfl->field_size = sizeof(epicsUInt32);
|
||||
pfl->u.v.field.dbf_ulong = arr[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ts_nanos(tsPrivate const *settings, db_field_log *pfl) {
|
||||
epicsUInt32 arr[2];
|
||||
ts_to_array(settings, &pfl->time, arr);
|
||||
pfl->field_type = DBF_ULONG;
|
||||
pfl->field_size = sizeof(epicsUInt32);
|
||||
pfl->u.v.field.dbf_ulong = arr[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ts_double(tsPrivate const *settings, db_field_log *pfl) {
|
||||
epicsUInt32 arr[2];
|
||||
ts_to_array(settings, &pfl->time, arr);
|
||||
pfl->field_type = DBF_DOUBLE;
|
||||
pfl->field_size = sizeof(epicsFloat64);
|
||||
pfl->u.v.field.dbf_double = arr[0] + arr[1] * 1e-9;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ts_array(tsPrivate const *settings, db_field_log *pfl) {
|
||||
pfl->field_type = DBF_ULONG;
|
||||
pfl->field_size = sizeof(epicsUInt32);
|
||||
pfl->type = dbfl_type_ref;
|
||||
pfl->u.r.pvt = NULL;
|
||||
pfl->u.r.field = allocTsArray();
|
||||
if (pfl->u.r.field) {
|
||||
pfl->no_elements = 2;
|
||||
pfl->dtor = freeTsArray;
|
||||
ts_to_array(settings, &pfl->time, (epicsUInt32*)pfl->u.r.field);
|
||||
} else {
|
||||
pfl->no_elements = 0;
|
||||
pfl->dtor = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ts_string(tsPrivate const *settings, db_field_log *pfl) {
|
||||
char const *fmt;
|
||||
char *field;
|
||||
size_t n;
|
||||
|
||||
switch (settings->str) {
|
||||
case tsStringEpics:
|
||||
fmt = "%Y-%m-%d %H:%M:%S.%06f";
|
||||
break;
|
||||
case tsStringIso:
|
||||
fmt = "%Y-%m-%dT%H:%M:%S.%06f%z";
|
||||
break;
|
||||
case tsStringInvalid:
|
||||
default:
|
||||
logicErrorMessage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
pfl->field_type = DBF_STRING;
|
||||
pfl->field_size = MAX_STRING_SIZE;
|
||||
pfl->type = dbfl_type_ref;
|
||||
pfl->u.r.pvt = NULL;
|
||||
pfl->u.r.field = allocString();
|
||||
|
||||
if (!pfl->u.r.field) {
|
||||
pfl->no_elements = 0;
|
||||
pfl->dtor = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
pfl->dtor = freeString;
|
||||
|
||||
field = (char *)pfl->u.r.field;
|
||||
n = epicsTimeToStrftime(field, MAX_STRING_SIZE, fmt, &pfl->time);
|
||||
if (!n) {
|
||||
field[0] = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static db_field_log *filter(void *pvt, dbChannel *chan, db_field_log *pfl) {
|
||||
tsPrivate *settings = (tsPrivate *)pvt;
|
||||
(void)chan;
|
||||
|
||||
switch (settings->mode) {
|
||||
case tsModeDouble:
|
||||
return replace_fl_value(pvt, pfl, ts_double);
|
||||
case tsModeSec:
|
||||
return replace_fl_value(pvt, pfl, ts_seconds);
|
||||
case tsModeNsec:
|
||||
return replace_fl_value(pvt, pfl, ts_nanos);
|
||||
case tsModeArray:
|
||||
return replace_fl_value(pvt, pfl, ts_array);
|
||||
case tsModeString:
|
||||
return replace_fl_value(pvt, pfl, ts_string);
|
||||
case tsModeGenerate:
|
||||
case tsModeInvalid:
|
||||
default:
|
||||
logicErrorMessage();
|
||||
db_delete_field_log(pfl);
|
||||
pfl = NULL;
|
||||
}
|
||||
|
||||
return pfl;
|
||||
}
|
||||
|
||||
|
||||
/* Only the "generate" mode is registered for the pre-queue chain as it creates
|
||||
it's own timestamp which should be as close to the event as possible */
|
||||
static void channelRegisterPre(dbChannel * chan, void *pvt,
|
||||
chPostEventFunc **cb_out, void **arg_out,
|
||||
db_field_log *probe) {
|
||||
tsPrivate *settings = (tsPrivate *)pvt;
|
||||
(void)chan;
|
||||
(void)arg_out;
|
||||
(void)probe;
|
||||
|
||||
*cb_out = settings->mode == tsModeGenerate ? generate : NULL;
|
||||
}
|
||||
|
||||
/* For other modes, the post-chain is fine as they only manipulate existing
|
||||
timestamps */
|
||||
static void channelRegisterPost(dbChannel *chan, void *pvt,
|
||||
chPostEventFunc **cb_out, void **arg_out,
|
||||
db_field_log *probe) {
|
||||
tsPrivate *settings = (tsPrivate *)pvt;
|
||||
(void)chan;
|
||||
|
||||
if (settings->mode == tsModeGenerate || settings->mode == tsModeInvalid) {
|
||||
*cb_out = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
*cb_out = filter;
|
||||
*arg_out = pvt;
|
||||
|
||||
/* Get rid of the value of the probe because we will be changing the
|
||||
datatype */
|
||||
if (probe->type == dbfl_type_ref && probe->dtor) {
|
||||
probe->dtor(probe);
|
||||
probe->dtor = NULL;
|
||||
}
|
||||
probe->no_elements = 1;
|
||||
probe->type = dbfl_type_val;
|
||||
|
||||
switch (settings->mode) {
|
||||
case tsModeArray:
|
||||
probe->no_elements = 2;
|
||||
/* fallthrough */
|
||||
case tsModeSec:
|
||||
case tsModeNsec:
|
||||
probe->field_type = DBF_ULONG;
|
||||
probe->field_size = sizeof(epicsUInt32);
|
||||
break;
|
||||
case tsModeDouble:
|
||||
probe->field_type = DBF_DOUBLE;
|
||||
probe->field_size = sizeof(epicsFloat64);
|
||||
break;
|
||||
case tsModeString:
|
||||
probe->field_type = DBF_STRING;
|
||||
probe->field_size = MAX_STRING_SIZE;
|
||||
break;
|
||||
case tsModeGenerate:
|
||||
case tsModeInvalid:
|
||||
// Already handled above, added here for completeness.
|
||||
default:
|
||||
logicErrorMessage();
|
||||
*cb_out = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void channel_report(dbChannel *chan, void *pvt, int level, const unsigned short indent)
|
||||
{
|
||||
printf("%*sTimestamp (ts)\n", indent, "");
|
||||
tsPrivate *settings = (tsPrivate *)pvt;
|
||||
(void)chan;
|
||||
(void)level;
|
||||
|
||||
printf("%*sTimestamp (ts): mode: %d, epoch: %d, str: %d\n",
|
||||
indent, "", settings->mode, settings->epoch, settings->str);
|
||||
}
|
||||
|
||||
static chfPluginIf pif = {
|
||||
NULL, /* allocPvt, */
|
||||
NULL, /* freePvt, */
|
||||
allocPvt,
|
||||
freePvt,
|
||||
|
||||
NULL, /* parse_error, */
|
||||
NULL, /* parse_ok, */
|
||||
NULL, /* parse_error, */
|
||||
parse_finished,
|
||||
|
||||
NULL, /* channel_open, */
|
||||
channelRegisterPre,
|
||||
NULL, /* channelRegisterPost, */
|
||||
channelRegisterPost,
|
||||
channel_report,
|
||||
NULL /* channel_close */
|
||||
};
|
||||
|
||||
static void tsInitialize(void)
|
||||
{
|
||||
chfPluginRegister("ts", &pif, NULL);
|
||||
freeListInitPvt(&private_free_list, sizeof(tsPrivate),
|
||||
ALLOC_NUM_ELEMENTS);
|
||||
freeListInitPvt(&ts_array_free_list, 2 * sizeof(epicsUInt32),
|
||||
ALLOC_NUM_ELEMENTS);
|
||||
freeListInitPvt(&string_free_list, MAX_STRING_SIZE,
|
||||
ALLOC_NUM_ELEMENTS);
|
||||
chfPluginRegister("ts", &pif, ts_args);
|
||||
}
|
||||
|
||||
epicsExportRegistrar(tsInitialize);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
=head1 Extensible Links
|
||||
=title Extensible IOC Database Links
|
||||
|
||||
The extensible link mechanism allows new kinds of record links to be created,
|
||||
using JSON for the link address syntax.
|
||||
@@ -24,13 +24,11 @@ The following additional link types are available in this release:
|
||||
=head2 Using JSON Links
|
||||
|
||||
When setting a record link field to a JSON link address, the link specification
|
||||
must appear inside a pair of braces C< {} > expressed as a JSON (L<JavaScript
|
||||
Object Notation|http://www.json.org/>) object, which allows link parameters to
|
||||
be defined as needed by the particular link type. When link fields are set from
|
||||
an IOC database file at initialization time, the field definitions may take
|
||||
advantage of a "relaxed JSON" syntax that reduces the number of double-quote
|
||||
characters required and maintains backwards compatibility with the older
|
||||
database file syntax.
|
||||
must appear inside a pair of braces C< {} > expressed as a JSON5 (L<JavaScript
|
||||
Object Notation|https://spec.json5.org/>) object, which allows link parameters
|
||||
to be defined as needed by the particular link type. When link fields are set in
|
||||
an IOC database file, the field value may take advantage of the JSON5 syntax to
|
||||
reduce the number of double-quote characters required.
|
||||
|
||||
|
||||
=head2 Link Type Reference
|
||||
@@ -68,10 +66,10 @@ results in an error.
|
||||
{const: "Pi"}
|
||||
{const: [1, 2.718281828459, 3.14159265358979]}
|
||||
{const: ["One", "e", "Pi"]}
|
||||
{const:[Inf, -Inf]})
|
||||
|
||||
The JSON syntax does not support Infinity or NaN values when parsing numbers,
|
||||
but (for scalars) it is possible to provide these in a string which will be
|
||||
converted to the desired double value at initialization, for example:
|
||||
The newer JSON5 syntax supports Infinity values when parsing numbers, so it is
|
||||
no longer necessary to quote these in a string, although that still works:
|
||||
|
||||
field(INP, {const:"Inf"})
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@ value is non-zero.
|
||||
|
||||
=back
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.14.10 .
|
||||
|
||||
=head2 Record-specific Menus
|
||||
|
||||
=head3 Menu aSubLFLG
|
||||
@@ -2353,10 +2355,12 @@ value, you would either delete the NOA specification, or specify it as "1".
|
||||
The associated subroutine code that uses the A field might look like this:
|
||||
|
||||
static long my_asub_routine(aSubRecord *prec) {
|
||||
long i, *a;
|
||||
long i;
|
||||
epicsInt32 *a;
|
||||
|
||||
double sum=0;
|
||||
...
|
||||
a = (long *)prec->a;
|
||||
a = (epicsInt32 *)prec->a;
|
||||
for (i=0; i<prec->noa; i++) {
|
||||
sum += a[i];
|
||||
}
|
||||
|
||||
@@ -157,6 +157,9 @@ CEIL: Ceiling (unary)
|
||||
=item *
|
||||
FLOOR: Floor (unary)
|
||||
|
||||
=item *
|
||||
FMOD: Floating point modulo (binary) Added in UNRELEASED
|
||||
|
||||
=item *
|
||||
LOG: Log base 10 (unary)
|
||||
|
||||
|
||||
@@ -183,6 +183,9 @@ CEIL: Ceiling (unary)
|
||||
=item *
|
||||
FLOOR: Floor (unary)
|
||||
|
||||
=item *
|
||||
FMOD: Floating point modulo (binary) Added in UNRELEASED
|
||||
|
||||
=item *
|
||||
LOG: Log base 10 (unary)
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "dbEvent.h"
|
||||
#include "dbFldTypes.h"
|
||||
#include "errMdef.h"
|
||||
#include "menuYesNo.h"
|
||||
#include "special.h"
|
||||
#include "recSup.h"
|
||||
#include "recGbl.h"
|
||||
@@ -166,9 +167,9 @@ static int compress_array(compressRecord *prec,
|
||||
}
|
||||
if (prec->n <= 0)
|
||||
prec->n = 1;
|
||||
n = prec->n;
|
||||
if (no_elements < n)
|
||||
if (no_elements < prec->n && prec->pbuf != menuYesNoYES)
|
||||
return 1; /*dont do anything*/
|
||||
n = no_elements;
|
||||
|
||||
/* determine number of samples to take */
|
||||
if (no_elements < nsam * n)
|
||||
@@ -272,7 +273,7 @@ static int array_average(compressRecord *prec,
|
||||
prec->inx = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int compress_scalar(struct compressRecord *prec,double *psource)
|
||||
{
|
||||
double value = *psource;
|
||||
@@ -292,19 +293,13 @@ static int compress_scalar(struct compressRecord *prec,double *psource)
|
||||
/* for scalars, Median not implemented => use average */
|
||||
case (compressALG_N_to_1_Average):
|
||||
case (compressALG_N_to_1_Median):
|
||||
if (inx == 0)
|
||||
*pdest = value;
|
||||
else {
|
||||
*pdest += value;
|
||||
if (inx + 1 >= prec->n)
|
||||
*pdest = *pdest / (inx + 1);
|
||||
}
|
||||
*pdest = (inx * (*pdest) + value) / (inx + 1);
|
||||
break;
|
||||
}
|
||||
inx++;
|
||||
if (inx >= prec->n) {
|
||||
if ((inx >= prec->n) || (prec->pbuf == menuYesNoYES)) {
|
||||
put_value(prec,pdest,1);
|
||||
prec->inx = 0;
|
||||
prec->inx = (inx >= prec->n) ? 0 : inx;
|
||||
return 0;
|
||||
} else {
|
||||
prec->inx = inx;
|
||||
|
||||
@@ -40,7 +40,7 @@ the beginning or the end of the VAL array.
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below.
|
||||
The record-specific fields are described below, grouped by functionality.
|
||||
|
||||
=recordtype compress
|
||||
|
||||
@@ -60,10 +60,6 @@ menu(bufferingALG) {
|
||||
}
|
||||
recordtype(compress) {
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below, grouped by functionality.
|
||||
|
||||
=head3 Scanning Parameters
|
||||
|
||||
The compression record has the standard fields for specifying under what
|
||||
@@ -85,7 +81,7 @@ algorithms which can be specified as follows:
|
||||
|
||||
The following fields determine what channel to read and how to compress the data:
|
||||
|
||||
=fields ALG, INP, NSAM, N, ILIL, IHIL, OFF, RES
|
||||
=fields ALG, INP, NSAM, N, ILIL, IHIL, OFF, RES, PBUF
|
||||
|
||||
As stated above, the ALG field specifies which algorithm to be performed on the data.
|
||||
|
||||
@@ -167,6 +163,23 @@ Compress N to 1 samples, taking the median value.
|
||||
|
||||
=back
|
||||
|
||||
The behaviour of the record for partially filled buffers depends on the field PBUF.
|
||||
If PBUF is set to NO, then the record will wait until the buffer is completely full
|
||||
before processing. If PBUF is set to YES, then it will start processing immediately.
|
||||
|
||||
For example, if ALG is set to C<<< N to 1 Average >>> with NSAM equal to 4, N equal
|
||||
to 1, and PBUF set to NO, then the first three times that the compress record is
|
||||
processed it will remain in an undefined state. On the fourth process, the average
|
||||
of all four records will be calculated and placed into the VAL field.
|
||||
|
||||
If PBUF is set to YES, then after each process the average of the first several
|
||||
elements will be calculated.
|
||||
|
||||
Note that PBUF has no impact on the C<<< Average >>> method. If one wishes to have a
|
||||
rolling average computed, then the best way to achieve that is with two compress
|
||||
records: a C<<< Circular buffer >>> which is linked to an C<<< N to 1 Average >>>
|
||||
record with PBUF set to YES.
|
||||
|
||||
The compression record keeps NSAM data samples.
|
||||
|
||||
The field N determines the number of elements to compress into each result.
|
||||
@@ -393,7 +406,15 @@ Scan forward link if necessary, set PACT FALSE, and return.
|
||||
interest(1)
|
||||
menu(compressALG)
|
||||
}
|
||||
field(BALG,DBF_MENU) {
|
||||
field(PBUF,DBF_MENU) {
|
||||
prompt("Use Partial buffers")
|
||||
promptgroup("30 - Action")
|
||||
special(SPC_RESET)
|
||||
interest(1)
|
||||
menu(menuYesNo)
|
||||
initial("NO")
|
||||
}
|
||||
field(BALG,DBF_MENU) {
|
||||
prompt("Buffering Algorithm")
|
||||
promptgroup("30 - Action")
|
||||
special(SPC_RESET)
|
||||
|
||||
@@ -13,6 +13,8 @@ The histogram record is used to store frequency counts of a signal into an array
|
||||
of arbitrary length. The user can configure the range of the signal value that
|
||||
the array will store. Anything outside this range will be ignored.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.15.0.1 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below.
|
||||
|
||||
@@ -14,6 +14,8 @@ from a hardware input.
|
||||
The record supports alarm limits, alarm filtering, graphics and control
|
||||
limits.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.16.1 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below.
|
||||
|
||||
@@ -13,6 +13,8 @@ This record type is normally used to send an integer value of up to 64 bits
|
||||
to an output device.
|
||||
The record supports alarm, drive, graphics and control limits.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.16.1 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below.
|
||||
|
||||
@@ -81,10 +81,14 @@ rset longoutRSET={
|
||||
};
|
||||
epicsExportAddress(rset,longoutRSET);
|
||||
|
||||
#define DONT_EXEC_OUTPUT 0
|
||||
#define EXEC_OUTPUT 1
|
||||
|
||||
static void checkAlarms(longoutRecord *prec);
|
||||
static void monitor(longoutRecord *prec);
|
||||
static long writeValue(longoutRecord *prec);
|
||||
static void convert(longoutRecord *prec, epicsInt32 value);
|
||||
static long conditional_write(longoutRecord *prec);
|
||||
|
||||
static long init_record(struct dbCommon *pcommon, int pass)
|
||||
{
|
||||
@@ -119,6 +123,9 @@ static long init_record(struct dbCommon *pcommon, int pass)
|
||||
prec->mlst = prec->val;
|
||||
prec->alst = prec->val;
|
||||
prec->lalm = prec->val;
|
||||
prec->pval = prec->val;
|
||||
prec->outpvt = EXEC_OUTPUT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -210,6 +217,14 @@ static long special(DBADDR *paddr, int after)
|
||||
recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm);
|
||||
return(0);
|
||||
}
|
||||
|
||||
/* Detect an output link re-direction (change) */
|
||||
if (dbGetFieldIndex(paddr) == longoutRecordOUT) {
|
||||
if ((after) && (prec->ooch == menuYesNoYES))
|
||||
prec->outpvt = EXEC_OUTPUT;
|
||||
return(0);
|
||||
}
|
||||
|
||||
default:
|
||||
recGblDbaddrError(S_db_badChoice, paddr, "longout: special");
|
||||
return(S_db_badChoice);
|
||||
@@ -381,7 +396,6 @@ static void monitor(longoutRecord *prec)
|
||||
|
||||
static long writeValue(longoutRecord *prec)
|
||||
{
|
||||
longoutdset *pdset = (longoutdset *) prec->dset;
|
||||
long status = 0;
|
||||
|
||||
if (!prec->pact) {
|
||||
@@ -391,7 +405,7 @@ static long writeValue(longoutRecord *prec)
|
||||
|
||||
switch (prec->simm) {
|
||||
case menuYesNoNO:
|
||||
status = pdset->write_longout(prec);
|
||||
status = conditional_write(prec);
|
||||
break;
|
||||
|
||||
case menuYesNoYES: {
|
||||
@@ -421,10 +435,61 @@ static long writeValue(longoutRecord *prec)
|
||||
|
||||
static void convert(longoutRecord *prec, epicsInt32 value)
|
||||
{
|
||||
/* check drive limits */
|
||||
/* check drive limits */
|
||||
if(prec->drvh > prec->drvl) {
|
||||
if (value > prec->drvh) value = prec->drvh;
|
||||
else if (value < prec->drvl) value = prec->drvl;
|
||||
}
|
||||
prec->val = value;
|
||||
}
|
||||
|
||||
/* Evaluate OOPT field to perform the write operation */
|
||||
static long conditional_write(longoutRecord *prec)
|
||||
{
|
||||
struct longoutdset *pdset = (struct longoutdset *) prec->dset;
|
||||
long status = 0;
|
||||
int doDevSupWrite = 0;
|
||||
|
||||
switch (prec->oopt)
|
||||
{
|
||||
case longoutOOPT_On_Change:
|
||||
/* Forces a write op if a change in the OUT field is detected OR is first process */
|
||||
if (prec->outpvt == EXEC_OUTPUT) {
|
||||
doDevSupWrite = 1;
|
||||
} else {
|
||||
/* Only write if value is different from the previous one */
|
||||
doDevSupWrite = (prec->val != prec->pval);
|
||||
}
|
||||
break;
|
||||
|
||||
case longoutOOPT_Every_Time:
|
||||
doDevSupWrite = 1;
|
||||
break;
|
||||
|
||||
case longoutOOPT_When_Zero:
|
||||
doDevSupWrite = (prec->val == 0);
|
||||
break;
|
||||
|
||||
case longoutOOPT_When_Non_zero:
|
||||
doDevSupWrite = (prec->val != 0);
|
||||
break;
|
||||
|
||||
case longoutOOPT_Transition_To_Zero:
|
||||
doDevSupWrite = ((prec->val == 0)&&(prec->pval != 0));
|
||||
break;
|
||||
|
||||
case longoutOOPT_Transition_To_Non_zero:
|
||||
doDevSupWrite = ((prec->val != 0)&&(prec->pval == 0));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (doDevSupWrite)
|
||||
status = pdset->write_longout(prec);
|
||||
|
||||
prec->pval = prec->val;
|
||||
prec->outpvt = DONT_EXEC_OUTPUT; /* reset status */
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,15 @@ limits.
|
||||
|
||||
=cut
|
||||
|
||||
menu(longoutOOPT) {
|
||||
choice(longoutOOPT_Every_Time,"Every Time")
|
||||
choice(longoutOOPT_On_Change,"On Change")
|
||||
choice(longoutOOPT_When_Zero,"When Zero")
|
||||
choice(longoutOOPT_When_Non_zero,"When Non-zero")
|
||||
choice(longoutOOPT_Transition_To_Zero,"Transition To Zero")
|
||||
choice(longoutOOPT_Transition_To_Non_zero,"Transition To Non-zero")
|
||||
}
|
||||
|
||||
recordtype(longout) {
|
||||
|
||||
=head2 Parameter Fields
|
||||
@@ -72,7 +81,50 @@ See L<Address
|
||||
Specification|https://docs.epics-controls.org/en/latest/guides/EPICS_Process_Database_Concepts.html#address-specification>
|
||||
for information on the format of hardware addresses and database links.
|
||||
|
||||
=fields OUT, DTYP
|
||||
=fields OUT, DTYP, OOPT, OOCH
|
||||
|
||||
=head4 Menu longoutOOPT
|
||||
|
||||
The OOPT field was added in EPICS UNRELEASED.
|
||||
|
||||
It determines the condition that causes the output link to be
|
||||
written to. It's a menu field that has six choices:
|
||||
|
||||
=menu longoutOOPT
|
||||
|
||||
=over
|
||||
|
||||
=item *
|
||||
C<Every Time> -- write output every time record is processed. (DEFAULT)
|
||||
|
||||
=item *
|
||||
C<On Change> -- write output every time VAL changes.
|
||||
|
||||
=item *
|
||||
C<When Zero> -- when record is processed, write output if VAL is zero.
|
||||
|
||||
=item *
|
||||
C<When Non-zero> -- when record is processed, write output if VAL is
|
||||
non-zero.
|
||||
|
||||
=item *
|
||||
C<Transition To Zero> -- when record is processed, write output only if VAL
|
||||
is zero and the last value was non-zero.
|
||||
|
||||
=item *
|
||||
C<Transition To Non-zero> -- when record is processed, write output only if
|
||||
VAL is non-zero and last value was zero.
|
||||
|
||||
=back
|
||||
|
||||
=head4 Changes in OUT field when OOPT = On Change
|
||||
|
||||
The OOCH field was added in EPICS UNRELEASED.
|
||||
|
||||
If OOCH is C<YES> (its default value) and the OOPT field is C<On Change>,
|
||||
the record will write to the device support the first time the record gets
|
||||
processed after its OUT link is modified, even when the output value has
|
||||
not actually changed.
|
||||
|
||||
=cut
|
||||
|
||||
@@ -94,6 +146,7 @@ for information on the format of hardware addresses and database links.
|
||||
}
|
||||
field(OUT,DBF_OUTLINK) {
|
||||
prompt("Output Specification")
|
||||
special(SPC_MOD)
|
||||
promptgroup("50 - Output")
|
||||
interest(1)
|
||||
}
|
||||
@@ -365,7 +418,7 @@ for more information on simulation mode and its fields.
|
||||
prompt("Sim. Mode Private")
|
||||
special(SPC_NOMOD)
|
||||
interest(4)
|
||||
extra("epicsCallback *simpvt")
|
||||
extra("epicsCallback *simpvt")
|
||||
}
|
||||
field(IVOA,DBF_MENU) {
|
||||
prompt("INVALID output action")
|
||||
@@ -378,6 +431,29 @@ for more information on simulation mode and its fields.
|
||||
promptgroup("50 - Output")
|
||||
interest(2)
|
||||
}
|
||||
field(PVAL,DBF_LONG) {
|
||||
prompt("Previous Value")
|
||||
}
|
||||
field(OUTPVT,DBF_NOACCESS) {
|
||||
prompt("Output Write Control Private")
|
||||
special(SPC_NOMOD)
|
||||
interest(4)
|
||||
extra("epicsEnum16 outpvt")
|
||||
}
|
||||
field(OOCH,DBF_MENU) {
|
||||
prompt("Output Exec. On Change (Opt)")
|
||||
promptgroup("50 - Output")
|
||||
interest(1)
|
||||
menu(menuYesNo)
|
||||
initial("1")
|
||||
}
|
||||
field(OOPT,DBF_MENU) {
|
||||
prompt("Output Execute Opt")
|
||||
promptgroup("50 - Output")
|
||||
interest(1)
|
||||
menu(longoutOOPT)
|
||||
initial("0")
|
||||
}
|
||||
|
||||
|
||||
=begin html
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
The long string input record is used to retrieve an arbitrary ASCII string with
|
||||
a maximum length of 65535 characters.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.15.0.2 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below, grouped by functionality.
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
The long string output record is used to write an arbitrary ASCII string with a
|
||||
maximum length of 65535 characters.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.15.0.2 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below, grouped by functionality.
|
||||
|
||||
@@ -271,7 +271,7 @@ static long special(DBADDR *paddr, int after)
|
||||
} else if(after==1 && fieldIndex >= mbboDirectRecordB0 && fieldIndex <= mbboDirectRecordB1F) {
|
||||
/* Adjust VAL corresponding to the bit changed */
|
||||
epicsUInt8 *pBn = (epicsUInt8 *) paddr->pfield;
|
||||
epicsUInt32 bit = 1 << (pBn - &prec->b0);
|
||||
epicsUInt32 bit = 1u << (pBn - &prec->b0);
|
||||
|
||||
/* Because this is !(VAL and PP), dbPut() will always post a monitor on this B* field
|
||||
* after we return. We must keep track of this change separately from MLST to handle
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
The printf record is used to generate and write a string using a format
|
||||
specification and parameters, analogous to the C C<printf()> function.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.15.0.2 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below, grouped by functionality.
|
||||
|
||||
@@ -162,7 +162,7 @@ static long process(struct dbCommon *pcommon)
|
||||
recGblFwdLink(prec);
|
||||
prec->pact = FALSE;
|
||||
|
||||
return 0;
|
||||
return status;
|
||||
}
|
||||
|
||||
static long special(DBADDR *paddr, int after)
|
||||
|
||||
@@ -209,6 +209,9 @@ int main(int argc, char *argv[])
|
||||
lazy_dbd(dbd_file);
|
||||
xmacro = "IOC=";
|
||||
xmacro += optarg;
|
||||
if (verbose) {
|
||||
std::cout<<"dbLoadRecords(\""<<exit_file<<"\", \""<<xmacro<<"\")\n";
|
||||
}
|
||||
errIf(dbLoadRecords(exit_file.c_str(), xmacro.c_str()),
|
||||
std::string("Failed to load: ")+exit_file);
|
||||
loadedDb = true;
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
# The name below is derived from the name of the SNL program
|
||||
# inside the source file, not from its filename. Here the
|
||||
# program is called sncExample, but is compiled in both the
|
||||
# sncExample.stt and sncProgram.st source files.
|
||||
registrar(sncExampleRegistrar)
|
||||
|
||||
@@ -3,7 +3,7 @@ To start the ioc from this directory execute the command
|
||||
|
||||
Alternatively make the st.cmd file directly executable with
|
||||
chmod +x st.cmd
|
||||
and check the executable name on the first line of the st.cmd file
|
||||
and check the executable name on the first line of the st.cmd file
|
||||
|
||||
You may need to change the name of the .dbd file given in the
|
||||
st.cmd's dbLoadDatabase() command before starting the ioc.
|
||||
|
||||
@@ -141,7 +141,8 @@ __END_DOCTYPE
|
||||
my $title = shift;
|
||||
return $podHtml->idify($title, 1);
|
||||
}
|
||||
} else { # Fall back to HTML
|
||||
}
|
||||
else { # Regular HTML
|
||||
$Pod::Simple::HTML::Content_decl = $contentType;
|
||||
$podHtml = EPICS::PodHtml->new();
|
||||
$podHtml->html_css('style.css');
|
||||
@@ -153,7 +154,13 @@ __END_DOCTYPE
|
||||
}
|
||||
|
||||
# Parse the Pod text from the root DBD object
|
||||
my $pod = join "\n", '=for html <div class="pod">', '',
|
||||
my $pod = join "\n",
|
||||
'=for html <div class="pod">',
|
||||
'',
|
||||
'L<EPICS Component Reference Manual|ComponentReference>',
|
||||
'',
|
||||
'=for html <hr>',
|
||||
'',
|
||||
map {
|
||||
# Handle a 'recordtype' Pod directive
|
||||
if (m/^ =recordtype \s+ (\w+) /x) {
|
||||
@@ -173,13 +180,16 @@ my $pod = join "\n", '=for html <div class="pod">', '',
|
||||
}
|
||||
elsif (m/^ =title \s+ (.*)/x) {
|
||||
$title = $1;
|
||||
"=head1 $title";
|
||||
"=head1 EPICS Reference: $title";
|
||||
}
|
||||
else {
|
||||
$_;
|
||||
}
|
||||
} $dbd->pod,
|
||||
'=for html </div>', '';
|
||||
'=for html </div><hr>',
|
||||
'',
|
||||
'L<EPICS Component Reference Manual|ComponentReference>',
|
||||
'';
|
||||
|
||||
$podHtml->force_title($podHtml->encode_entities($title));
|
||||
$podHtml->perldoc_url_prefix('');
|
||||
@@ -307,7 +317,7 @@ sub DBD::Recfield::writable {
|
||||
|
||||
=pod
|
||||
|
||||
=head1 Converting Wiki Record Reference to POD
|
||||
=head1 Writing Record Reference as POD
|
||||
|
||||
If you open the src/std/rec/aiRecord.dbd.pod file in your favourite plain text
|
||||
editor you'll see what input was required to generate the aiRecord.html file.
|
||||
@@ -320,13 +330,14 @@ When we add POD markup to a record type, we rename its *Record.dbd file to
|
||||
system to find it by its new name. The POD content is effectively just a new
|
||||
kind of comment that appears in .dbd.pod files, which the formatter knows how to
|
||||
convert into HTML. The build also generates a plain *Record.dbd file from this
|
||||
same input file by stripping out all of the POD markup.
|
||||
same input file by stripping out all of the POD markup, so make sure to remove
|
||||
the old *Record.dbd file from your source directory.
|
||||
|
||||
Documentation for Perl's POD markup standard can be found online at
|
||||
L<https://perldoc.perl.org/perlpod.html> or you may be able to type 'perldoc
|
||||
perlpod' into a Linux command-line to see the same text. We added a few POD
|
||||
keywords of our own to handle the table generation, and I'll cover those briefly
|
||||
below.
|
||||
L<https://perldoc.perl.org/perlpod> or you may be able to type
|
||||
C<perldoc perlpod> at a Linux command-line to see the same text.
|
||||
We added a few POD keywords of our own to handle the table generation, and we'll
|
||||
cover those briefly below.
|
||||
|
||||
POD text can appear almost anywhere in a dbd.pod file. It always starts with a
|
||||
line "=[keyword] [additional text...]" where [keyword] is "title", "head1"
|
||||
@@ -390,4 +401,8 @@ table that lists all the choices found in the named menu. Any MENU fields in the
|
||||
field tables that refer to a locally-defined menu will generate a link to a
|
||||
document section which must be titled "Menu [menuName]".
|
||||
|
||||
The "title" keyword should only appear once in each file, it sets the document
|
||||
title as well as generating a "head1" heading with "EPICS Reference:" pre-pended
|
||||
to the text.
|
||||
|
||||
=cut
|
||||
|
||||
@@ -146,7 +146,7 @@ __EOF__
|
||||
" prt->papFldDes[${rn}Record${fn}]->size = " .
|
||||
"sizeof(prec->${cn});\n" .
|
||||
" prt->papFldDes[${rn}Record${fn}]->offset = " .
|
||||
"(unsigned short)((char *)&prec->${cn} - (char *)prec);"
|
||||
"(unsigned short)offsetof(${rn}Record, ${cn});"
|
||||
} @fields), << "__EOF__";
|
||||
|
||||
prt->rec_size = sizeof(*prec);
|
||||
|
||||
@@ -46,6 +46,7 @@ if ($opt_D) { # Output dependencies only
|
||||
}
|
||||
|
||||
$Text::Wrap::columns = 75;
|
||||
$Text::Wrap::huge = 'overflow';
|
||||
|
||||
# Eliminate chars not allowed in C symbol names
|
||||
my $c_bad_ident_chars = '[^0-9A-Za-z_]';
|
||||
|
||||
@@ -131,13 +131,15 @@ static long xscanio_read(xRecord *prec)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static xdset devxScanIO = {
|
||||
static xdset
|
||||
devxScanIO_excessively_long_symbol_name_for_testing_code_generation = {
|
||||
5, NULL, NULL,
|
||||
&xscanio_init_record,
|
||||
&xscanio_get_ioint_info,
|
||||
&xscanio_read
|
||||
};
|
||||
epicsExportAddress(dset, devxScanIO);
|
||||
epicsExportAddress(dset,
|
||||
devxScanIO_excessively_long_symbol_name_for_testing_code_generation);
|
||||
|
||||
/* basic DTYP="Soft Channel" */
|
||||
static long xsoft_init_record(xRecord *prec)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
device(x, CONSTANT, devxSoft, "Soft Channel")
|
||||
device(x, INST_IO , devxScanIO, "Scan I/O")
|
||||
device(x, INST_IO, devxScanIO_excessively_long_symbol_name_for_testing_code_generation, "Scan I/O")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2021 Cosylab d.d
|
||||
* Copyright (c) 2010 Brookhaven National Laboratory.
|
||||
* Copyright (c) 2010 Helmholtz-Zentrum Berlin
|
||||
* fuer Materialien und Energie GmbH.
|
||||
@@ -9,9 +10,11 @@
|
||||
|
||||
/*
|
||||
* Author: Ralph Lange <Ralph.Lange@bessy.de>
|
||||
* Author: Jure Varlec <jure.varlec@cosylab.com>
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "dbStaticLib.h"
|
||||
#include "dbAccessDefs.h"
|
||||
@@ -25,11 +28,24 @@
|
||||
#include "testMain.h"
|
||||
#include "osiFileName.h"
|
||||
|
||||
/* A fill pattern for setting a field log to something "random". */
|
||||
#define PATTERN 0x55
|
||||
|
||||
void filterTest_registerRecordDeviceDriver(struct dbBase *);
|
||||
/* Use a "normal" timestamp for testing. What results from filling the field log
|
||||
with the above pattern is a timestamp that causes problems with
|
||||
epicsTimeToStrftime() on some platforms. */
|
||||
static epicsTimeStamp const test_ts = { 616600420, 998425354 };
|
||||
|
||||
static db_field_log fl;
|
||||
typedef int (*TypeCheck)(const db_field_log *pfl);
|
||||
typedef int (*ValueCheck)(const db_field_log *pfl, const epicsTimeStamp *ts);
|
||||
|
||||
typedef struct {
|
||||
char const *channel;
|
||||
TypeCheck type_check;
|
||||
ValueCheck value_check;
|
||||
} TestSpec;
|
||||
|
||||
void filterTest_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
static int fl_equal(const db_field_log *pfl1, const db_field_log *pfl2) {
|
||||
return !(memcmp(pfl1, pfl2, sizeof(db_field_log)));
|
||||
@@ -42,21 +58,210 @@ static int fl_equal_ex_ts(const db_field_log *pfl1, const db_field_log *pfl2) {
|
||||
return fl_equal(&fl1, pfl2);
|
||||
}
|
||||
|
||||
MAIN(tsTest)
|
||||
{
|
||||
static void fl_reset(db_field_log *pfl) {
|
||||
memset(pfl, PATTERN, sizeof(*pfl));
|
||||
pfl->time = test_ts;
|
||||
}
|
||||
|
||||
static void test_generate_filter(const chFilterPlugin *plug) {
|
||||
dbChannel *pch;
|
||||
chFilter *filter;
|
||||
const chFilterPlugin *plug;
|
||||
char ts[] = "ts";
|
||||
ELLNODE *node;
|
||||
chPostEventFunc *cb_out = NULL;
|
||||
void *arg_out = NULL;
|
||||
db_field_log fl;
|
||||
db_field_log fl1;
|
||||
db_field_log *pfl2;
|
||||
epicsTimeStamp stamp, now;
|
||||
dbEventCtx evtctx;
|
||||
ELLNODE *node;
|
||||
chPostEventFunc *cb_out = NULL;
|
||||
void *arg_out = NULL;
|
||||
char const *chan_name = "x.VAL{ts:{}}";
|
||||
|
||||
testPlan(12);
|
||||
testOk(!!(pch = dbChannelCreate(chan_name)),
|
||||
"dbChannel with plugin ts created");
|
||||
testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
|
||||
|
||||
fl_reset(&fl);
|
||||
fl1 = fl;
|
||||
node = ellFirst(&pch->filters);
|
||||
filter = CONTAINER(node, chFilter, list_node);
|
||||
plug->fif->channel_register_post(filter, &cb_out, &arg_out, &fl1);
|
||||
plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl1);
|
||||
testOk(!!(cb_out) && !(arg_out),
|
||||
"register_pre registers one filter w/o argument");
|
||||
testOk(fl_equal(&fl1, &fl),
|
||||
"register_pre does not change field_log data type");
|
||||
|
||||
testOk(!(dbChannelOpen(pch)), "dbChannel with plugin ts opened");
|
||||
node = ellFirst(&pch->pre_chain);
|
||||
filter = CONTAINER(node, chFilter, pre_node);
|
||||
testOk((ellCount(&pch->pre_chain) == 1 && filter->pre_arg == NULL),
|
||||
"ts has one filter w/o argument in pre chain");
|
||||
testOk((ellCount(&pch->post_chain) == 0), "ts has no filter in post chain");
|
||||
|
||||
fl_reset(&fl);
|
||||
fl1 = fl;
|
||||
pfl2 = dbChannelRunPreChain(pch, &fl1);
|
||||
testOk(pfl2 == &fl1, "ts filter does not drop or replace field_log");
|
||||
testOk(fl_equal_ex_ts(&fl1, pfl2),
|
||||
"ts filter does not change field_log data");
|
||||
|
||||
testOk(!!(pfl2 = db_create_read_log(pch)), "create field log from channel");
|
||||
stamp = pfl2->time;
|
||||
db_delete_field_log(pfl2);
|
||||
|
||||
pfl2 = dbChannelRunPreChain(pch, &fl1);
|
||||
epicsTimeGetCurrent(&now);
|
||||
testOk(epicsTimeDiffInSeconds(&pfl2->time, &stamp) >= 0 &&
|
||||
epicsTimeDiffInSeconds(&now, &pfl2->time) >= 0,
|
||||
"ts filter sets time stamp to \"now\"");
|
||||
|
||||
dbChannelDelete(pch);
|
||||
}
|
||||
|
||||
static void test_value_filter(const chFilterPlugin *plug, const char *chan_name,
|
||||
TypeCheck tc_func, ValueCheck vc_func) {
|
||||
dbChannel *pch;
|
||||
chFilter *filter;
|
||||
db_field_log fl;
|
||||
db_field_log fl2;
|
||||
db_field_log *pfl;
|
||||
epicsTimeStamp ts;
|
||||
ELLNODE *node;
|
||||
chPostEventFunc *cb_out = NULL;
|
||||
void *arg_out = NULL;
|
||||
|
||||
testDiag("Channel %s", chan_name);
|
||||
|
||||
testOk(!!(pch = dbChannelCreate(chan_name)),
|
||||
"dbChannel with plugin ts created");
|
||||
testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
|
||||
|
||||
fl_reset(&fl);
|
||||
fl.type = dbfl_type_val;
|
||||
node = ellFirst(&pch->filters);
|
||||
filter = CONTAINER(node, chFilter, list_node);
|
||||
plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl);
|
||||
plug->fif->channel_register_post(filter, &cb_out, &arg_out, &fl);
|
||||
testOk(!!(cb_out) && arg_out,
|
||||
"register_post registers one filter with argument");
|
||||
testOk(tc_func(&fl), "register_post gives correct field type");
|
||||
|
||||
testOk(!(dbChannelOpen(pch)), "dbChannel with plugin ts opened");
|
||||
node = ellFirst(&pch->post_chain);
|
||||
filter = CONTAINER(node, chFilter, post_node);
|
||||
testOk((ellCount(&pch->post_chain) == 1 && filter->post_arg != NULL),
|
||||
"ts has one filter with argument in post chain");
|
||||
testOk((ellCount(&pch->pre_chain) == 0), "ts has no filter in pre chain");
|
||||
|
||||
fl_reset(&fl);
|
||||
fl.type = dbfl_type_val;
|
||||
ts = fl.time;
|
||||
fl2 = fl;
|
||||
pfl = dbChannelRunPostChain(pch, &fl);
|
||||
testOk(pfl == &fl, "ts filter does not drop or replace field_log");
|
||||
testOk(tc_func(pfl), "ts filter gives correct field type");
|
||||
testOk((pfl->time.secPastEpoch == fl2.time.secPastEpoch &&
|
||||
pfl->time.nsec == fl2.time.nsec && pfl->stat == fl2.stat &&
|
||||
pfl->sevr == fl2.sevr),
|
||||
"ts filter does not touch non-value fields of field_log");
|
||||
testOk(vc_func(pfl, &ts), "ts filter gives correct field value");
|
||||
|
||||
dbChannelDelete(pch);
|
||||
}
|
||||
|
||||
static int type_check_double(const db_field_log *pfl) {
|
||||
return pfl->type == dbfl_type_val
|
||||
&& pfl->field_type == DBR_DOUBLE
|
||||
&& pfl->field_size == sizeof(epicsFloat64)
|
||||
&& pfl->no_elements == 1;
|
||||
}
|
||||
|
||||
static int value_check_double(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
epicsFloat64 flt = pfl->u.v.field.dbf_double;
|
||||
epicsFloat64 nsec = (flt - (epicsUInt32)(flt)) * 1e9;
|
||||
return ts->secPastEpoch == (epicsUInt32)(flt)
|
||||
&& fabs(ts->nsec - nsec) < 1000.; /* allow loss of precision */
|
||||
}
|
||||
|
||||
static int type_check_sec_nsec(const db_field_log *pfl) {
|
||||
return pfl->type == dbfl_type_val
|
||||
&& pfl->field_type == DBR_ULONG
|
||||
&& pfl->field_size == sizeof(epicsUInt32)
|
||||
&& pfl->no_elements == 1;
|
||||
}
|
||||
|
||||
static int value_check_sec(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
return ts->secPastEpoch == pfl->u.v.field.dbf_ulong;
|
||||
}
|
||||
|
||||
static int value_check_nsec(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
return ts->nsec == pfl->u.v.field.dbf_ulong;
|
||||
}
|
||||
|
||||
static int type_check_array(const db_field_log *pfl) {
|
||||
return pfl->field_type == DBR_ULONG
|
||||
&& pfl->field_size == sizeof(epicsUInt32)
|
||||
&& pfl->no_elements == 2;
|
||||
}
|
||||
|
||||
static int value_check_array(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
epicsUInt32 *arr = (epicsUInt32*)pfl->u.r.field;
|
||||
return pfl->type == dbfl_type_ref
|
||||
&& pfl->u.r.field != NULL
|
||||
&& pfl->dtor != NULL
|
||||
&& pfl->u.r.pvt == NULL
|
||||
&& ts->secPastEpoch == arr[0]
|
||||
&& ts->nsec == arr[1];
|
||||
}
|
||||
|
||||
static int value_check_unix(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
epicsUInt32 *arr = (epicsUInt32 *)pfl->u.r.field;
|
||||
return pfl->type == dbfl_type_ref
|
||||
&& pfl->u.r.field != NULL
|
||||
&& pfl->dtor != NULL
|
||||
&& pfl->u.r.pvt == NULL
|
||||
&& ts->secPastEpoch == arr[0] - POSIX_TIME_AT_EPICS_EPOCH
|
||||
&& ts->nsec == arr[1];
|
||||
}
|
||||
|
||||
static int type_check_string(const db_field_log *pfl) {
|
||||
return pfl->field_type == DBR_STRING
|
||||
&& pfl->field_size == MAX_STRING_SIZE
|
||||
&& pfl->no_elements == 1;
|
||||
}
|
||||
|
||||
static int value_check_string(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
/* We can only verify the type, not the value, because using strptime()
|
||||
might be problematic. */
|
||||
(void)ts;
|
||||
return pfl->type == dbfl_type_ref
|
||||
&& pfl->u.r.field != NULL
|
||||
&& pfl->dtor != NULL
|
||||
&& pfl->u.r.pvt == NULL;
|
||||
}
|
||||
|
||||
MAIN(tsTest) {
|
||||
int i;
|
||||
char ts[] = "ts";
|
||||
dbEventCtx evtctx;
|
||||
const chFilterPlugin *plug;
|
||||
|
||||
static TestSpec const tests[] = {
|
||||
{"x.VAL{ts:{\"num\": \"dbl\"}}", type_check_double, value_check_double},
|
||||
{"x.VAL{ts:{\"num\": \"sec\"}}", type_check_sec_nsec, value_check_sec},
|
||||
{"x.VAL{ts:{\"num\": \"nsec\"}}", type_check_sec_nsec, value_check_nsec},
|
||||
{"x.VAL{ts:{\"num\": \"ts\"}}", type_check_array, value_check_array},
|
||||
{"x.VAL{ts:{\"num\": \"ts\", \"epoch\": \"epics\"}}", type_check_array, value_check_array},
|
||||
{"x.VAL{ts:{\"num\": \"ts\", \"epoch\": \"unix\"}}", type_check_array, value_check_unix},
|
||||
{"x.VAL{ts:{\"str\": \"epics\"}}", type_check_string, value_check_string},
|
||||
#if !(defined _MSC_VER && _MSC_VER <= 1700)
|
||||
{"x.VAL{ts:{\"str\": \"iso\"}}", type_check_string, value_check_string},
|
||||
#endif
|
||||
};
|
||||
|
||||
static int const num_value_tests = sizeof(tests) / sizeof(tests[0]);
|
||||
|
||||
testPlan(12 /* test_generate_filter() */
|
||||
+ num_value_tests * 11 /* test_value_filter() */);
|
||||
|
||||
testdbPrepare();
|
||||
|
||||
@@ -77,41 +282,12 @@ MAIN(tsTest)
|
||||
testAbort("plugin '%s' not registered", ts);
|
||||
testPass("plugin '%s' registered correctly", ts);
|
||||
|
||||
testOk(!!(pch = dbChannelCreate("x.VAL{ts:{}}")), "dbChannel with plugin ts created");
|
||||
testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
|
||||
test_generate_filter(plug);
|
||||
|
||||
memset(&fl, PATTERN, sizeof(fl));
|
||||
fl1 = fl;
|
||||
node = ellFirst(&pch->filters);
|
||||
filter = CONTAINER(node, chFilter, list_node);
|
||||
plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl1);
|
||||
testOk(!!(cb_out) && !(arg_out), "register_pre registers one filter w/o argument");
|
||||
testOk(fl_equal(&fl1, &fl), "register_pre does not change field_log data type");
|
||||
|
||||
testOk(!(dbChannelOpen(pch)), "dbChannel with plugin ts opened");
|
||||
node = ellFirst(&pch->pre_chain);
|
||||
filter = CONTAINER(node, chFilter, pre_node);
|
||||
testOk((ellCount(&pch->pre_chain) == 1 && filter->pre_arg == NULL),
|
||||
"ts has one filter w/o argument in pre chain");
|
||||
testOk((ellCount(&pch->post_chain) == 0), "ts has no filter in post chain");
|
||||
|
||||
memset(&fl, PATTERN, sizeof(fl));
|
||||
fl1 = fl;
|
||||
pfl2 = dbChannelRunPreChain(pch, &fl1);
|
||||
testOk(pfl2 == &fl1, "ts filter does not drop or replace field_log");
|
||||
testOk(fl_equal_ex_ts(&fl1, pfl2), "ts filter does not change field_log data");
|
||||
|
||||
testOk(!!(pfl2 = db_create_read_log(pch)), "create field log from channel");
|
||||
stamp = pfl2->time;
|
||||
db_delete_field_log(pfl2);
|
||||
|
||||
pfl2 = dbChannelRunPreChain(pch, &fl1);
|
||||
epicsTimeGetCurrent(&now);
|
||||
testOk(epicsTimeDiffInSeconds(&pfl2->time, &stamp) >= 0 &&
|
||||
epicsTimeDiffInSeconds(&now, &pfl2->time) >= 0,
|
||||
"ts filter sets time stamp to \"now\"");
|
||||
|
||||
dbChannelDelete(pch);
|
||||
for (i = 0; i < num_value_tests; ++i) {
|
||||
TestSpec const *t = &tests[i];
|
||||
test_value_filter(plug, t->channel, t->type_check, t->value_check);
|
||||
}
|
||||
|
||||
db_close_events(evtctx);
|
||||
|
||||
|
||||
@@ -86,6 +86,13 @@ testHarness_SRCS += seqTest.c
|
||||
TESTFILES += ../seqTest.db
|
||||
TESTS += seqTest
|
||||
|
||||
TESTPROD_HOST += longoutTest
|
||||
longoutTest_SRCS += longoutTest.c
|
||||
longoutTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
|
||||
testHarness_SRCS += longoutTest.c
|
||||
TESTFILES += ../longoutTest.db
|
||||
TESTS += longoutTest
|
||||
|
||||
TARGETS += $(COMMON_DIR)/asTestIoc.dbd
|
||||
DBDDEPENDS_FILES += asTestIoc.dbd$(DEP)
|
||||
asTestIoc_DBD += base.dbd
|
||||
@@ -160,6 +167,16 @@ asyncproctest_SRCS += asyncproctest_registerRecordDeviceDriver.cpp
|
||||
TESTFILES += $(COMMON_DIR)/asyncproctest.dbd ../asyncproctest.db
|
||||
TESTS += asyncproctest
|
||||
|
||||
TARGETS += $(COMMON_DIR)/subproctest.dbd
|
||||
DBDDEPENDS_FILES += subproctest.dbd$(DEP)
|
||||
subproctest_DBD += base.dbd
|
||||
TESTPROD_HOST += subproctest
|
||||
subproctest_SRCS += subproctest.c
|
||||
subproctest_SRCS += subproctest_registerRecordDeviceDriver.cpp
|
||||
TESTFILES += $(COMMON_DIR)/subproctest.dbd ../subproctest.db
|
||||
TESTS += subproctest
|
||||
|
||||
|
||||
TESTPROD_HOST += linkFilterTest
|
||||
linkFilterTest_SRCS += linkFilterTest.c
|
||||
linkFilterTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
|
||||
|
||||
@@ -5,17 +5,24 @@
|
||||
* in file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "cantProceed.h"
|
||||
#include "dbUnitTest.h"
|
||||
#include "testMain.h"
|
||||
#include "dbLock.h"
|
||||
#include "errlog.h"
|
||||
#include "dbAccess.h"
|
||||
#include "epicsMath.h"
|
||||
#include "menuYesNo.h"
|
||||
|
||||
#include "aiRecord.h"
|
||||
#include "waveformRecord.h"
|
||||
#include "compressRecord.h"
|
||||
|
||||
#define testDEq(A,B,D) testOk(fabs((A)-(B))<(D), #A " (%f) ~= " #B " (%f)", A, B)
|
||||
#define fetchRecordOrDie(recname, addr) if (dbNameToAddr(recname, &addr)) {testAbort("Unknown PV '%s'", recname);}
|
||||
|
||||
|
||||
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
@@ -33,8 +40,7 @@ void checkArrD(const char *pv, long elen, double a, double b, double c, double d
|
||||
expect[2] = c;
|
||||
expect[3] = d;
|
||||
|
||||
if (dbNameToAddr(pv, &addr))
|
||||
testAbort("Unknown PV '%s'", pv);
|
||||
fetchRecordOrDie(pv, addr);
|
||||
|
||||
if (dbGet(&addr, DBR_DOUBLE, buf, NULL, &nReq, NULL))
|
||||
testAbort("Failed to get '%s'", pv);
|
||||
@@ -66,8 +72,7 @@ void checkArrI(const char *pv, long elen, epicsInt32 a, epicsInt32 b, epicsInt32
|
||||
expect[2] = c;
|
||||
expect[3] = d;
|
||||
|
||||
if (dbNameToAddr(pv, &addr))
|
||||
testAbort("Unknown PV '%s'", pv);
|
||||
fetchRecordOrDie(pv, addr);
|
||||
|
||||
if (dbGet(&addr, DBR_LONG, buf, NULL, &nReq, NULL))
|
||||
testAbort("Failed to get '%s'", pv);
|
||||
@@ -85,6 +90,24 @@ void checkArrI(const char *pv, long elen, epicsInt32 a, epicsInt32 b, epicsInt32
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
writeToWaveform(DBADDR *addr, long count, ...) {
|
||||
va_list args;
|
||||
long i;
|
||||
double *values = (double *)callocMustSucceed(count, sizeof(double), "writeToWaveform");
|
||||
|
||||
va_start(args, count);
|
||||
for (i=0; i< count; i++) {
|
||||
values[i] = va_arg(args, double);
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
dbScanLock(addr->precord);
|
||||
dbPut(addr, DBR_DOUBLE, values, count);
|
||||
dbScanUnlock(addr->precord);
|
||||
free(values);
|
||||
}
|
||||
|
||||
static
|
||||
void testFIFOCirc(void)
|
||||
{
|
||||
@@ -100,9 +123,9 @@ void testFIFOCirc(void)
|
||||
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
|
||||
testdbReadDatabase("compressTest.db", NULL, "ALG=Circular Buffer,BALG=FIFO Buffer,NSAM=4");
|
||||
testdbReadDatabase("compressTest.db", NULL, "INP=ai,ALG=Circular Buffer,BALG=FIFO Buffer,NSAM=4");
|
||||
|
||||
vrec = (aiRecord*)testdbRecordPtr("val");
|
||||
vrec = (aiRecord*)testdbRecordPtr("ai");
|
||||
crec = (compressRecord*)testdbRecordPtr("comp");
|
||||
|
||||
eltc(0);
|
||||
@@ -230,9 +253,9 @@ void testLIFOCirc(void)
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
|
||||
testdbReadDatabase("compressTest.db", NULL,
|
||||
"ALG=Circular Buffer,BALG=LIFO Buffer,NSAM=4");
|
||||
"INP=ai,ALG=Circular Buffer,BALG=LIFO Buffer,NSAM=4");
|
||||
|
||||
vrec = (aiRecord*)testdbRecordPtr("val");
|
||||
vrec = (aiRecord*)testdbRecordPtr("ai");
|
||||
crec = (compressRecord*)testdbRecordPtr("comp");
|
||||
|
||||
eltc(0);
|
||||
@@ -346,10 +369,278 @@ void testLIFOCirc(void)
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
void
|
||||
testArrayAverage(void) {
|
||||
DBADDR wfaddr, caddr;
|
||||
|
||||
testDiag("Test Array Average");
|
||||
testdbPrepare();
|
||||
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=Average,BALG=FIFO Buffer,NSAM=4,N=2");
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
fetchRecordOrDie("wf", wfaddr);
|
||||
fetchRecordOrDie("comp", caddr);
|
||||
|
||||
writeToWaveform(&wfaddr, 4, 1., 2., 3., 4.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
|
||||
writeToWaveform(&wfaddr, 4, 2., 4., 6., 8.);
|
||||
|
||||
dbProcess(caddr.precord);
|
||||
|
||||
checkArrD("comp", 4, 1.5, 3., 4.5, 6.);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
testIocShutdownOk();
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
void
|
||||
testNto1Average(void) {
|
||||
double buf = 0.0;
|
||||
long nReq = 1;
|
||||
DBADDR wfaddr, caddr;
|
||||
|
||||
testDiag("Test Average");
|
||||
|
||||
testdbPrepare();
|
||||
|
||||
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
|
||||
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
|
||||
testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=N to 1 Average,BALG=FIFO Buffer,NSAM=1,N=4");
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
fetchRecordOrDie("wf", wfaddr);
|
||||
fetchRecordOrDie("comp", caddr);
|
||||
|
||||
testDiag("Test incomplete input data");
|
||||
|
||||
writeToWaveform(&wfaddr, 3, 1., 2., 3.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
testOk1(nReq == 0);
|
||||
testDEq(buf, 0., 0.01);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
testDiag("Test complete input data");
|
||||
|
||||
writeToWaveform(&wfaddr, 4, 1., 2., 3., 4.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
nReq = 1;
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
testDEq(buf, 2.5, 0.01);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
testDiag("Test single input data");
|
||||
|
||||
writeToWaveform(&wfaddr, 1, 5.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
nReq = 1;
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
// Assert that nothing has changed from before
|
||||
testDEq(buf, 2.5, 0.01);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
testIocShutdownOk();
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
void
|
||||
testNto1AveragePartial(void) {
|
||||
double buf = 0.0;
|
||||
long nReq = 1;
|
||||
DBADDR wfaddr, caddr;
|
||||
|
||||
testDiag("Test Average, Partial");
|
||||
|
||||
testdbPrepare();
|
||||
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=N to 1 Average,BALG=FIFO Buffer,NSAM=1,N=4,PBUF=YES");
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
testDiag("Test incomplete input data");
|
||||
|
||||
fetchRecordOrDie("wf", wfaddr);
|
||||
fetchRecordOrDie("comp", caddr);
|
||||
|
||||
writeToWaveform(&wfaddr, 3, 1., 2., 3.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
testDEq(buf, 2.0, 0.01);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
testDiag("Test single entry from wf record");
|
||||
|
||||
writeToWaveform(&wfaddr, 1, 6.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
testDEq(buf, 6.0, 0.01);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
testIocShutdownOk();
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
void
|
||||
testNto1LowValue(void) {
|
||||
double buf = 0.0;
|
||||
long nReq = 1;
|
||||
DBADDR wfaddr, caddr;
|
||||
|
||||
testDiag("Test 'N to 1 Low Value'");
|
||||
|
||||
testdbPrepare();
|
||||
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=N to 1 Low Value,BALG=FIFO Buffer,NSAM=1,N=4");
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
fetchRecordOrDie("wf", wfaddr);
|
||||
fetchRecordOrDie("comp", caddr);
|
||||
|
||||
testDiag("Test full array");
|
||||
|
||||
writeToWaveform(&wfaddr, 4, 1., 2., 3., 4.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
testDEq(buf, 1.0, 0.01);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
writeToWaveform(&wfaddr, 4, 4., 3., 2., 1.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
testDEq(buf, 1.0, 0.01);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
testDiag("Test partial data with PBUF set to NO");
|
||||
|
||||
writeToWaveform(&wfaddr, 3, 5., 6., 7.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
// We confirm that this hasn't changed i.e. the dbProcess above did nothing
|
||||
testDEq(buf, 1.0, 0.01);
|
||||
|
||||
testDiag("Test partial data with PBUF set to YES");
|
||||
|
||||
((compressRecord *)caddr.precord)->pbuf = menuYesNoYES;
|
||||
|
||||
dbProcess(caddr.precord);
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
testDEq(buf, 5.0, 0.01);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
|
||||
testIocShutdownOk();
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
void
|
||||
testAIAveragePartial(void) {
|
||||
double buf = 0.;
|
||||
double data[5] = {1., 2., 3., 4., 5.};
|
||||
/*
|
||||
* Note that the fifth dbPut essentially resets the circular buffer, so the
|
||||
* average is once again the average of the _first_ entry alone.
|
||||
*/
|
||||
double expected[5] = {1., 1.5, 2., 2.5, 5.};
|
||||
long nReq = 1;
|
||||
int i;
|
||||
DBADDR aiaddr, caddr;
|
||||
|
||||
testDiag("Test 'N to 1 Average' with analog in, PBUF=YES");
|
||||
|
||||
testdbPrepare();
|
||||
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("compressTest.db", NULL, "INP=ai,ALG=N to 1 Average,BALG=FIFO Buffer,NSAM=1,N=4,PBUF=YES");
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
fetchRecordOrDie("ai", aiaddr);
|
||||
fetchRecordOrDie("comp", caddr);
|
||||
|
||||
for (i = 0; i < 5; i++) {
|
||||
dbScanLock(aiaddr.precord);
|
||||
dbPut(&aiaddr, DBR_DOUBLE, &data[i], 1);
|
||||
dbScanUnlock(aiaddr.precord);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
testDEq(buf, expected[i], 0.01);
|
||||
}
|
||||
|
||||
testIocShutdownOk();
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
MAIN(compressTest)
|
||||
{
|
||||
testPlan(116);
|
||||
testPlan(132);
|
||||
testFIFOCirc();
|
||||
testLIFOCirc();
|
||||
testArrayAverage();
|
||||
testNto1Average();
|
||||
testNto1AveragePartial();
|
||||
testAIAveragePartial();
|
||||
testNto1LowValue();
|
||||
return testDone();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
record(ai, "val") {}
|
||||
record(ai, "ai") {}
|
||||
record(waveform, "wf") {
|
||||
field(FTVL, "DOUBLE")
|
||||
field(NELM, "4")
|
||||
}
|
||||
record(compress, "comp") {
|
||||
field(INP, "val NPP")
|
||||
field(INP, "$(INP) NPP")
|
||||
field(ALG, "$(ALG)")
|
||||
field(PBUF,"$(PBUF=NO)")
|
||||
field(BALG,"$(BALG)")
|
||||
field(NSAM,"$(NSAM)")
|
||||
field(N, "$(N=1)")
|
||||
}
|
||||
|
||||
270
modules/database/test/std/rec/longoutTest.c
Normal file
270
modules/database/test/std/rec/longoutTest.c
Normal file
@@ -0,0 +1,270 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2020 Joao Paulo Martins
|
||||
* EPICS BASE is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
|
||||
#include "dbUnitTest.h"
|
||||
#include "testMain.h"
|
||||
#include "dbLock.h"
|
||||
#include "errlog.h"
|
||||
#include "dbAccess.h"
|
||||
#include "epicsMath.h"
|
||||
#include "menuYesNo.h"
|
||||
|
||||
#include "longoutRecord.h"
|
||||
|
||||
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
static void test_oopt_everytime(void){
|
||||
/* reset rec processing counter_a */
|
||||
testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0);
|
||||
|
||||
/* write the same value two times */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16);
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16);
|
||||
|
||||
/* write two times with different values*/
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17);
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18);
|
||||
|
||||
/* Test if the counter_a was processed 4 times */
|
||||
testdbGetFieldEqual("counter_a", DBF_DOUBLE, 4.0);
|
||||
|
||||
// number of tests = 6
|
||||
}
|
||||
|
||||
static void test_oopt_onchange(void){
|
||||
/* change OOPT to On Change */
|
||||
testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_On_Change);
|
||||
|
||||
/* reset rec processing counter_a */
|
||||
testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0);
|
||||
|
||||
/* write the same value two times */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16);
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16);
|
||||
|
||||
/* Test if the counter_a was processed only once */
|
||||
testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0);
|
||||
|
||||
/* write two times with different values*/
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17);
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18);
|
||||
|
||||
/* Test if the counter_a was processed 1 + 2 times */
|
||||
testdbGetFieldEqual("counter_a", DBF_DOUBLE, 3.0);
|
||||
|
||||
//number of tests 8
|
||||
}
|
||||
|
||||
static void test_oopt_whenzero(void){
|
||||
testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_When_Zero);
|
||||
|
||||
/* reset rec processing counter_a */
|
||||
testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0);
|
||||
|
||||
/* write zero two times */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0);
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0);
|
||||
|
||||
/* Test if the counter_a was processed twice */
|
||||
testdbGetFieldEqual("counter_a", DBF_DOUBLE, 2.0);
|
||||
|
||||
/* write two times with non-zero values*/
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17);
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18);
|
||||
|
||||
/* Test if the counter_a was still processed 2 times */
|
||||
testdbGetFieldEqual("counter_a", DBF_DOUBLE, 2.0);
|
||||
|
||||
//number of tests 8
|
||||
}
|
||||
|
||||
static void test_oopt_whennonzero(void){
|
||||
testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_When_Non_zero);
|
||||
|
||||
/* reset rec processing counter_a */
|
||||
testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0);
|
||||
|
||||
/* write zero two times */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0);
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0);
|
||||
|
||||
/* Test if the counter_a was never processed */
|
||||
testdbGetFieldEqual("counter_a", DBF_DOUBLE, 0.0);
|
||||
|
||||
/* write two times with non-zero values*/
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17);
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18);
|
||||
|
||||
/* Test if the counter_a was still processed 2 times */
|
||||
testdbGetFieldEqual("counter_a", DBF_DOUBLE, 2.0);
|
||||
|
||||
//number of tests 8
|
||||
}
|
||||
|
||||
static void test_oopt_when_transition_zero(void){
|
||||
testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_Transition_To_Zero);
|
||||
|
||||
/* reset rec processing counter_a */
|
||||
testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0);
|
||||
|
||||
/* write non-zero then zero */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16);
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0);
|
||||
|
||||
/* Test if the counter_a was processed */
|
||||
testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0);
|
||||
|
||||
/* write another transition to zero */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17);
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0);
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17);
|
||||
|
||||
/* Test if the counter_a was processed once more */
|
||||
testdbGetFieldEqual("counter_a", DBF_DOUBLE, 2.0);
|
||||
|
||||
//number of tests 9
|
||||
}
|
||||
|
||||
static void test_oopt_when_transition_nonzero(void){
|
||||
testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_Transition_To_Non_zero);
|
||||
|
||||
/* write non-zero to start fresh */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16);
|
||||
|
||||
/* reset rec processing counter_a */
|
||||
testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0);
|
||||
|
||||
/* write non-zero then zero */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17);
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0);
|
||||
|
||||
/* Test if the counter_a was never processed */
|
||||
testdbGetFieldEqual("counter_a", DBF_DOUBLE, 0.0);
|
||||
|
||||
/* write a transition to non-zero */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18);
|
||||
|
||||
/* Test if the counter_a was processed */
|
||||
testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0);
|
||||
|
||||
//number of tests 8
|
||||
}
|
||||
|
||||
static void test_changing_out_field(void){
|
||||
/* change OOPT to On Change */
|
||||
testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_On_Change);
|
||||
|
||||
/* write an initial value */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16);
|
||||
|
||||
/* reset rec processing counters */
|
||||
testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0);
|
||||
testdbPutFieldOk("counter_b.VAL", DBF_DOUBLE, 0.0);
|
||||
|
||||
/* write the same value */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16);
|
||||
|
||||
/* Test if the counter was never processed */
|
||||
testdbGetFieldEqual("counter_a", DBF_DOUBLE, 0.0);
|
||||
|
||||
/* change the OUT link to another counter */
|
||||
testdbPutFieldOk("longout_rec.OUT", DBF_STRING, "counter_b.B PP");
|
||||
|
||||
/* write the same value */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16);
|
||||
|
||||
/* Test if the counter was processed once */
|
||||
testdbGetFieldEqual("counter_b", DBF_DOUBLE, 1.0);
|
||||
|
||||
/* write the same value */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16);
|
||||
|
||||
/* Test if the counter was not processed again */
|
||||
testdbGetFieldEqual("counter_b", DBF_DOUBLE, 1.0);
|
||||
|
||||
/* Set option to write ON CHANGE even when the OUT link was changed */
|
||||
testdbPutFieldOk("longout_rec.OOCH", DBF_ENUM, menuYesNoNO);
|
||||
|
||||
/* write an initial value */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16);
|
||||
|
||||
/* reset rec processing counters */
|
||||
testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0);
|
||||
testdbPutFieldOk("counter_b.VAL", DBF_DOUBLE, 0.0);
|
||||
|
||||
/* write the same value */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16);
|
||||
|
||||
/* Test if the counter_b was never processed */
|
||||
testdbGetFieldEqual("counter_b", DBF_DOUBLE, 0.0);
|
||||
|
||||
/* change back the OUT link to counter_a */
|
||||
testdbPutFieldOk("longout_rec.OUT", DBF_STRING, "counter_a.B PP");
|
||||
|
||||
/* write the same value */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16);
|
||||
|
||||
/* Test if the counter was never processed */
|
||||
testdbGetFieldEqual("counter_a", DBF_DOUBLE, 0.0);
|
||||
|
||||
/* write the same value */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16);
|
||||
|
||||
/* Test if the counter was not processed again */
|
||||
testdbGetFieldEqual("counter_a", DBF_DOUBLE, 0.0);
|
||||
|
||||
/* write new value */
|
||||
testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17);
|
||||
|
||||
/* Test if the counter was processed once */
|
||||
testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0);
|
||||
|
||||
/* reset rec processing counters */
|
||||
testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0);
|
||||
|
||||
/* test if record with OOPT == On Change will
|
||||
write to output at its first process */
|
||||
testdbPutFieldOk("longout_rec2.VAL", DBF_LONG, 16);
|
||||
|
||||
/* Test if the counter was processed once */
|
||||
testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0);
|
||||
|
||||
/* write the same value */
|
||||
testdbPutFieldOk("longout_rec2.VAL", DBF_LONG, 16);
|
||||
|
||||
/* Test if the counter was not processed again */
|
||||
testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0);
|
||||
|
||||
//number of tests 29
|
||||
}
|
||||
|
||||
MAIN(longoutTest) {
|
||||
|
||||
testPlan(6+8+8+8+9+8+29);
|
||||
|
||||
testdbPrepare();
|
||||
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
|
||||
testdbReadDatabase("longoutTest.db", NULL, NULL);
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
test_oopt_everytime();
|
||||
test_oopt_onchange();
|
||||
test_oopt_whenzero();
|
||||
test_oopt_whennonzero();
|
||||
test_oopt_when_transition_zero();
|
||||
test_oopt_when_transition_nonzero();
|
||||
test_changing_out_field();
|
||||
|
||||
testIocShutdownOk();
|
||||
testdbCleanup();
|
||||
|
||||
return testDone();
|
||||
}
|
||||
25
modules/database/test/std/rec/longoutTest.db
Normal file
25
modules/database/test/std/rec/longoutTest.db
Normal file
@@ -0,0 +1,25 @@
|
||||
record(calc, "counter_a") {
|
||||
field(INPA, "counter_a")
|
||||
field(CALC, "A+1")
|
||||
field(SCAN, "Passive")
|
||||
}
|
||||
|
||||
record(calc, "counter_b") {
|
||||
field(INPA, "counter_b")
|
||||
field(CALC, "A+1")
|
||||
field(SCAN, "Passive")
|
||||
}
|
||||
|
||||
record(longout, "longout_rec") {
|
||||
field(VAL, "0")
|
||||
field(OUT, "counter_a.B PP")
|
||||
field(PINI, "YES")
|
||||
}
|
||||
|
||||
record(longout, "longout_rec2") {
|
||||
field(VAL, "16")
|
||||
field(OUT, "counter_a.B PP")
|
||||
field(PINI, "NO")
|
||||
field(OOPT, "On Change")
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ MAIN(mbbioDirectTest)
|
||||
|
||||
testDiag("##### clear bit 31 (0x1f) #####");
|
||||
putN("do%u.B1F", N, 0);
|
||||
value &= ~(1<<31);
|
||||
value &= ~(1u<<31u);
|
||||
testN("val%d", N, value);
|
||||
testmbbioRecords(N, value);
|
||||
|
||||
|
||||
55
modules/database/test/std/rec/subproctest.c
Normal file
55
modules/database/test/std/rec/subproctest.c
Normal file
@@ -0,0 +1,55 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2022 UChicago Argonne LLC, as Operator of Argonne
|
||||
* National Laboratory.
|
||||
* SPDX-License-Identifier: EPICS
|
||||
* EPICS BASE is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
|
||||
/* This test covers tests related to invoking subrecords
|
||||
*/
|
||||
|
||||
#include <testMain.h>
|
||||
#include <dbUnitTest.h>
|
||||
#include <dbAccess.h>
|
||||
#include <iocsh.h>
|
||||
#include "registryFunction.h"
|
||||
#include <subRecord.h>
|
||||
|
||||
static
|
||||
long subproc(subRecord *prec)
|
||||
{
|
||||
prec->proc = 77;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void subproctest_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
MAIN(subproctest)
|
||||
{
|
||||
testPlan(2);
|
||||
|
||||
testdbPrepare();
|
||||
|
||||
testdbReadDatabase("subproctest.dbd", NULL, NULL);
|
||||
subproctest_registerRecordDeviceDriver(pdbbase);
|
||||
registryFunctionAdd("subproc", (REGISTRYFUNCTION) subproc);
|
||||
testdbReadDatabase("subproctest.db", NULL, "TPRO=0");
|
||||
|
||||
testIocInitOk();
|
||||
testDiag("===== Test that invalid link in INPA field fails a put request ======");
|
||||
|
||||
testdbPutFieldFail(-1, "InvalidINPARec.PROC", DBF_LONG, 1);
|
||||
|
||||
/* Since the put to PROC above fails, subproc() never runs
|
||||
* and the value of PROC will not be set by subproc(). However,
|
||||
* the testdbPutField call above goes through, so we get a partial
|
||||
* result of the PROC field being left as 1. */
|
||||
testdbGetFieldEqual("InvalidINPARec.PROC", DBF_LONG, 1);
|
||||
|
||||
testIocShutdownOk();
|
||||
|
||||
testdbCleanup();
|
||||
|
||||
return testDone();
|
||||
}
|
||||
4
modules/database/test/std/rec/subproctest.db
Normal file
4
modules/database/test/std/rec/subproctest.db
Normal file
@@ -0,0 +1,4 @@
|
||||
record(sub, "InvalidINPARec") {
|
||||
field(SNAM, "subproc")
|
||||
field(INPA, "nonexistent")
|
||||
}
|
||||
@@ -60,7 +60,19 @@ extern void *POSIX_Init(void *argument);
|
||||
#define CONFIGURE_APPLICATION_NEEDS_STUB_DRIVER
|
||||
#define CONFIGURE_APPLICATION_NEEDS_ZERO_DRIVER
|
||||
|
||||
/* Max FDs cannot exceed FD_SETSIZE from newlib (64) */
|
||||
/* Note: The select() system call can only be used with the first FD_SETSIZE
|
||||
* File Descriptors (newlib default is 64). Beginning RTEMS 5.1, FDs are
|
||||
* allocated sequentially. So changing this CONFIGURE parameter such
|
||||
* that CONFIGURE_MAXIMUM_FILE_DESCRIPTORS >= FD_SETSIZE will likely
|
||||
* cause applications making select() calls to fault at some point.
|
||||
*
|
||||
* IOC core components (libca and RSRV) do not make select() calls.
|
||||
*
|
||||
* Applications and driver code using poll() or other socket
|
||||
* multiplexers do not share this limitation.
|
||||
*
|
||||
* cf. https://github.com/epics-base/epics-base/issues/300
|
||||
*/
|
||||
#define CONFIGURE_MAXIMUM_FILE_DESCRIPTORS 64
|
||||
#define CONFIGURE_IMFS_ENABLE_MKFIFO 2
|
||||
|
||||
|
||||
@@ -54,10 +54,10 @@ static struct rtems_bsdnet_ifconfig loopback_config = {
|
||||
*/
|
||||
#if defined(__i386__)
|
||||
extern int
|
||||
rtems_ne2kpci_driver_attach (struct rtems_bsdnet_ifconfig *config, int attach);
|
||||
rtems_ne2kpci_driver_attach (struct rtems_bsdnet_ifconfig *config);
|
||||
static struct rtems_bsdnet_ifconfig ne2k_driver_config = {
|
||||
"ne2", /* name */
|
||||
rtems_ne2kpci_driver_attach, /* attach function */
|
||||
(void*)&rtems_ne2kpci_driver_attach, /* attach function */
|
||||
#if RTEMS_VERSION_INT<VERSION_INT(4,11,0,0)
|
||||
&loopback_config, /* link to next interface */
|
||||
#else
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
#include "postfix.h"
|
||||
#include "asLib.h"
|
||||
|
||||
#undef ECHO /* from termios.h */
|
||||
|
||||
int asCheckClientIP;
|
||||
|
||||
static epicsMutexId asLock;
|
||||
|
||||
@@ -240,6 +240,11 @@ LIBCOM_API long
|
||||
*ptop = floor(*ptop);
|
||||
break;
|
||||
|
||||
case FMOD:
|
||||
top = *ptop--;
|
||||
*ptop = fmod(*ptop, top);
|
||||
break;
|
||||
|
||||
case FINITE:
|
||||
nargs = *pinst++;
|
||||
top = finite(*ptop);
|
||||
|
||||
@@ -104,6 +104,7 @@ static const ELEMENT operands[] = {
|
||||
{"F", 0, 0, 1, OPERAND, FETCH_F},
|
||||
{"FINITE", 7, 8, 0, VARARG_OPERATOR,FINITE},
|
||||
{"FLOOR", 7, 8, 0, UNARY_OPERATOR, FLOOR},
|
||||
{"FMOD", 7, 8, -1, UNARY_OPERATOR, FMOD},
|
||||
{"G", 0, 0, 1, OPERAND, FETCH_G},
|
||||
{"H", 0, 0, 1, OPERAND, FETCH_H},
|
||||
{"I", 0, 0, 1, OPERAND, FETCH_I},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user