Compare commits

...

127 Commits

Author SHA1 Message Date
Michael Davidsaver
42604fc794 Allow clang with GCC compilerSpecific.h
Some checks failed
Check EditorConfig / editorconfig (push) Failing after 1s
Base / Cross linux-aarch64 (push) Failing after 2s
Base / Cross linux-arm gnueabi (push) Failing after 1s
Base / Cross linux-arm gnueabihf (push) Failing after 2s
Base / CentOS-7 (push) Failing after 1s
Base / Fedora-33 (push) Failing after 2s
Base / Fedora-latest (push) Failing after 1s
Base / Ub-20 clang-10 C++11 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-4.10 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-5.1 xilinx_zynq_a9_qemu (push) Has been cancelled
Base / Win2019 mingw (push) Has been cancelled
Base / Ub-20 gcc-9 + MinGW (push) Has been cancelled
Base / Ub-20 gcc-9 unsigned char (push) Has been cancelled
Base / Ub-20 gcc-9 C++11, static (push) Has been cancelled
Base / Ub-20 gcc-9 + MinGW, static (push) Has been cancelled
Base / MacOS clang-12 (push) Has been cancelled
Base / Ub-20 clang-10 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-5.1 beatnik (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-4.9 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-5.1 pc686 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-5.1 uC5282 (push) Has been cancelled
Base / Win2019 MSC-19 (push) Has been cancelled
Base / Win2019 MSC-19, debug (push) Has been cancelled
Base / Win2019 MSC-19, static (push) Has been cancelled
Makes it easier to run clang derivative analysis tools
on builds configured for GCC.
2023-06-13 08:47:13 -07:00
Michael Davidsaver
4ecc0daa79 make mapDBFToDBR[] const 2023-06-13 08:47:13 -07:00
Michael Davidsaver
5a1f3ecc8b doc: note when some record types were introduced 2023-05-18 11:57:41 -07:00
Michael Davidsaver
cb97d662a7 doc 2023-05-01 10:31:01 -07:00
Michael Davidsaver
d4fab0d20e iocsh: dbCompleteRecord() missing NULL check 2023-05-01 10:31:01 -07:00
Michael Davidsaver
0c13e6ba6c iocsh: tab completion handle iocshArgArgv 2023-05-01 10:31:01 -07:00
Michael Davidsaver
8f1243da40 epicsSingleton: eliminate global ctor with >= c++11 2023-05-01 10:25:29 -07:00
Michael Davidsaver
fe9995c0b5 Update recommendation for CONFIGURE_MAXIMUM_FILE_DESCRIPTORS 2023-05-01 09:06:59 -07:00
Freddie Akeroyd
f56412d6a5 WIN32: use FlsAlloc() to cleanup epicsThreadOSD
Adjust macros for compiling for older MSVC/Win SDK versions

Try to cover missing fibres include in 7.0 SDK

Support Windows XP and above.
Also removed explicit define of _WIN32_WINNT in code if it has not
been passed on compile line. This is possibly a matter for
further discussion
2023-05-01 09:03:52 -07:00
Érico Nogueira
07d18c55ba Clean whitespace in makeBaseApp template README 2023-05-01 09:03:52 -07:00
Minijackson
9f97f25669 ci: add cross-compilation tests for aarch64, arm soft and hard float 2023-05-01 09:03:52 -07:00
912a82c0b5 replace deprecated decrementing volatile with atomic decrement 2023-05-01 09:03:52 -07:00
acf2241fd0 Some archs define ECHO in termios.h 2023-05-01 09:03:52 -07:00
Eva Lott
b878295d06 Added the new annotation EPICS_PRINTF_FMT 2023-05-01 09:03:45 -07:00
f41f11c7f6 fix compiler warning on 32 bit systems 2023-04-28 09:35:49 -05:00
Michael Davidsaver
216359974c update release notes 2023-04-18 13:11:59 -05:00
Torsten Bögershausen
17ad04505e Change compiler for FreeBSD 13: Use clang
FreeBSD 13 uses clang, not gcc, any more.
GNU_DIR must be set to /usr/local

Note: This change touches both the x86 and the x86_64 files.
It was tested on 'amd64' system only, which is x86_64
2023-04-18 09:51:33 -05:00
Michael Davidsaver
3500a02034 iocsh: expose dbCreateAlias 2023-04-18 09:51:28 -05:00
Michael Davidsaver
52b18d56a0 dbCreateAlias fixup error handling 2023-04-18 09:51:28 -05:00
Michael Davidsaver
5507646ce7 posix: optimize epicsThreadOnce()
Use atomic ops to short circuit when already initialized
2023-04-18 09:51:24 -05:00
Michael Davidsaver
625c2ef159 epicsThreadPerform: time epicsThreadOnce() 2023-04-18 09:51:24 -05:00
Jure Varlec
bdaca51d96 Update shareLib API docs, directing the reader to makeAPIheader.pl 2023-04-18 09:51:21 -05:00
Jure Varlec
9655b78e11 Update release notes: ts filter 2023-04-18 09:51:07 -05:00
Jure Varlec
e11f88017d ts filter: port to the new db_field_log 2023-04-18 09:51:07 -05:00
Jure Varlec
8a3020033e ts filter: replace cantProceed with a non-fatal error msg 2023-04-18 09:51:07 -05:00
Jure Varlec
bd1af9ac95 ts filter: fix unused variable warnings 2023-04-18 09:51:07 -05:00
Jure Varlec
d5959ca20a ts filter: handle calloc failures 2023-04-18 09:51:07 -05:00
Jure Varlec
e10dcede7d ts filter: clear the dtor field after destruction 2023-04-18 09:51:07 -05:00
Jure Varlec
c042b08ab0 Extend timestamp filter, giving access to the record timestamp 2023-04-18 09:51:07 -05:00
Michael Davidsaver
5eff3803a8 update release notes 2023-03-10 12:02:29 +00:00
Michael Davidsaver
151256533f renamed hidden pdbbase in dbd parser
avoid confusion with non-static pdbbase global
2023-03-10 11:00:58 +00:00
Michael Davidsaver
3b484f58d3 for links, treat "" the same as unset. 2023-03-10 11:00:58 +00:00
Michael Davidsaver
d3f93746a8 1<<31 upsets ubsan 2023-03-10 10:57:35 +00:00
Michael Davidsaver
f99a1cb0f3 dbdToRecordtypeH use offsetof()
quiets UB sanitizer
2023-03-10 10:57:35 +00:00
Michael Davidsaver
531a769007 fix rtems_ne2kpci_driver_attach prototype 2023-03-10 10:57:35 +00:00
Henrique Silva
3e51491628 Fix generalTimeReport interest_level argument type
Setting to iocshArgInt actually makes the function verbosity to be able
to be controlled by the value
2023-03-10 10:56:58 +00:00
0b01fb20db make buffer large enough for any argument value 2023-03-10 10:56:58 +00:00
Minijackson
c7a769e5da editorconfig: add workflow to check if config is upheld 2023-03-10 10:56:58 +00:00
Minijackson
a9fd57a865 editorconfig: add initial version forcing final newlines
See #337
2023-03-10 10:56:58 +00:00
e862f0e95f fix warning "if clause does not guard..." 2023-03-10 10:56:58 +00:00
172597e0e6 avoid accessing dbr_text[type] when type is out of range 2023-03-10 10:56:58 +00:00
bcdeeed206 fix missing newline at end of file 2023-03-10 10:56:58 +00:00
Michael Davidsaver
b6626e4f60 dbEvent: try to detect possible "stall"
event_read() should not return if the last callback
was delivered with eventsRemaining!=0
2023-03-10 10:56:58 +00:00
Michael Davidsaver
e1c1bb8b1b dbEvent: correct eventsRemaining
Only pass eventsRemaining when no queued events have been canceled.
Also possible race bt accessing ev_que with locking.
2023-03-10 10:56:58 +00:00
Torsten Bögershausen
90ae51e8f2 MacOs: Use readline from MacPorts
Commit b38ff09f6e and
commit d9ca8a70f0 introduced the TAB
completion in iocsh.

Commit 1f75813a4d enabled it for MacOs
having readline installed via HomeBrew.

This commit enables it for MacPorts.
2023-03-10 10:56:58 +00:00
Brendan Chandler
832abbd3b1 Return an error if subrecord processing fails due to bad INP links
If a sub record has an invalid INPx link, it was silently failing (and
not running the proc function).  This change plumbs in the error, so
the put fails and the user knows something went wrong.
2023-03-10 10:56:58 +00:00
Michael Davidsaver
52cc68433f COMMANDLINE_LIBRARY fallback to $(wildcard $(GNU_DIR) 2023-03-10 09:52:30 +00:00
Michael Davidsaver
f430389ee7 iocShutdown(): Always stop worker threads 2023-03-10 09:52:08 +00:00
Michael Davidsaver
bded79f14d dbScan join threads 2023-03-10 09:52:08 +00:00
Michael Davidsaver
2ff44cb386 callback join threads 2023-03-10 09:52:08 +00:00
Michael Davidsaver
a9ade9669a switch dbScan to epicsThreadCreateOpt() 2023-03-10 09:52:08 +00:00
Michael Davidsaver
f902d70006 switch callback to epicsThreadCreateOpt() 2023-03-10 09:52:08 +00:00
Michael Davidsaver
e22d74310b RTEMS: ensure epicsThreadMustJoin() short-circuits 2023-03-10 09:52:08 +00:00
Simon Rose
e5ad12e638 Updated RELEASE_NOTES 2023-03-10 09:51:08 +00:00
Simon Rose
b963a4564e Added commend about PBUF having no effect on 'Average' algorithm 2023-03-10 09:51:08 +00:00
Simon Rose
579c125b01 Updated documentation 2023-03-10 09:51:08 +00:00
Simon Rose
84d9617375 Added one more put/process to go over the buffer length
Note that it is not really a circular buffer in this case, but
a full reset of the buffer to the beginning. This matches the
documentation, but it seems valuable to add an explicit test for
this case.
2023-03-10 09:51:08 +00:00
Simon Rose
d66e90a016 Fixing 'error C2057: expected constant expression' error 2023-03-10 09:51:08 +00:00
Simon Rose
373e5440ac General cleanup 2023-03-10 09:51:08 +00:00
Simon Rose
dec23501e1 Added test for array_average 2023-03-10 09:51:08 +00:00
Simon Rose
11a4bed9aa compress_scalar for average works correctly now 2023-03-10 09:51:08 +00:00
Simon Rose
bf4a4c6b78 Added failing test for partial ai average 2023-03-10 09:51:08 +00:00
Simon Rose
b54d4b9a24 Added test for low value 2023-03-10 09:51:08 +00:00
Simon Rose
84f4771691 Single input data test passes 2023-03-10 09:51:08 +00:00
Simon Rose
1dc34a02e2 Add test path for single input data 2023-03-10 09:51:08 +00:00
Simon Rose
3ab22818da Added failing test for partially filled buffer average 2023-03-10 09:51:08 +00:00
Simon Rose
1ab474638d Initial test for averaging 2023-03-10 09:51:08 +00:00
Michael Davidsaver
eea361bf5e Com: Allow runtime bypass of freeListLib
By environment or iocsh variable.
2023-03-10 09:48:27 +00:00
Michael Davidsaver
d9052f7105 update ci-scripts 2023-03-09 14:26:45 +00:00
Michael Davidsaver
fa00572780 update pvData 2023-03-06 15:54:34 +00:00
Ralph Lange
636f5517b2 Add QT Creator 9.x dir to .gitignore 2023-03-06 09:30:02 +00:00
Andrew Johnson
718da5c9be Convert RecordReference.md to ComponentReference.pod
That .pod file will now be converted to .html using the same style as
the individual local documents that it links to.

The dbdToHtml output files now include a header and footer that link to
the ComponentReference.html file (they started pointing to the original
RecordReference.html but that was broken on most people's builds because
we weren't converting that from the original Markdown version).

This commit also adjusts documentation/Makefile to use the build system
properly, and reorders the Doxygen main page index.
2023-02-04 22:56:19 -06:00
Michael Davidsaver
e5335ce760 GHA: Add -funsigned-char build 2023-02-01 09:25:10 -08:00
3dbc9ea264 iocsh: fix argument splitting
Since commit 60128ee9 "Com: separate iocsh argument splitting",
iocsh is broken on VxWorks (tested with version 6.9.4.12)

Any command prints the error "Unbalanced quote."

> iocsh
epics> echo
Unbalanced quote.
epics> "echo"
Unbalanced quote.
epics> "echo
Unbalanced quote.
epics> echo bla
Unbalanced quote.
epics> echo 1 2 3
Unbalanced quote.
epics> exit
Unbalanced quote.
2023-02-01 09:25:10 -08:00
Michael Davidsaver
80da400f9c dbLock: avoid possibly lost log prior to cantProceed 2023-02-01 09:25:10 -08:00
Michael Davidsaver
5787125bbb doc 2023-02-01 09:25:10 -08:00
Andrew Johnson
bc54524270 Fix bad JSON char detection in dbStatic
Thanks to Dirk Zimoch for pointing this out.
2023-01-24 11:08:13 -06:00
Andrew Johnson
a2d53c05f6 Explain SNC registration in template 2023-01-24 11:06:48 -06:00
Andrew Johnson
3293a29d59 Darwin: Remove '-undefined dynamic_lookup' from SHRLIB_LDFLAGS
It's been deprecated by Apple in latest XCode and generates warning
messages when creating .dylib libraries, but they don't yet have a
solution for use when creating loadable libraries (.bundle files).
2023-01-19 11:05:21 -06:00
Andrew Johnson
550e902bf3 iocLogPrefix: skip "already set" warning if prefix identical 2023-01-19 11:00:14 -06:00
Andrew Johnson
84e5cc0b69 softIoc -v -x prefix should show loading exit_file 2023-01-19 10:56:46 -06:00
Andrew Johnson
4b63882f28 Appveyor config fixes 2023-01-06 15:28:15 -06:00
Andrew Johnson
fb742beae3 Fix epicsThreadOnceTest.c
Replace epicsThreadSleep() with proper thread synchronization.
The main thread was reading runCount without taking lock first.
Taking the lock before accessing doneCount might not be necessary
given the epicsEventMustWait(), but not 100% sure.
2023-01-06 15:24:56 -06:00
Andrew Johnson
7ef0c80630 errSymLib: status 0 => "Ok" 2022-12-31 19:14:57 -06:00
Andrew Johnson
1f75813a4d Have macOS builds use Homebrew's GNU readline if present 2022-12-31 19:12:49 -06:00
Andrew Johnson
34ad8e2347 Edits to Release Notes and postfix.h documentation 2022-12-31 19:10:49 -06:00
Andrew Johnson
413f14e4ae Remove now-unused pdset variable. 2022-12-29 18:10:38 -06:00
Andrew Johnson
c22895d499 Adjust documentation about longout feature 2022-12-29 18:04:48 -06:00
Andrew Johnson
e63184e518 Merge PR #63, longout.OOPT 2022-12-29 16:31:42 -06:00
Andrew Johnson
755a4541c5 Update the RecordReference.md index; add links and filters. 2022-12-28 15:53:48 -06:00
Andrew Johnson
05cd7edf71 Update channel filter documentation, adding $ syntax 2022-12-28 15:52:30 -06:00
Andrew Johnson
5759726a89 Update link documentation 2022-12-28 15:43:35 -06:00
Andrew Johnson
14e7926d22 Add RecRef links to the top & bottom of each reference page 2022-12-28 15:30:25 -06:00
Andrew Johnson
c068fe3525 capr.pl: Fetch link fields as long strings 2022-12-28 12:43:38 -06:00
Andrew Johnson
c2364d9d1c Add license header to new epicsReadlinePvt.h 2022-12-28 12:42:51 -06:00
Michael Davidsaver
6be0372257 doc 2022-12-21 09:50:07 -08:00
Michael Davidsaver
e994ad78db GHA update 2022-12-21 08:15:26 -08:00
Michael Davidsaver
49fddaa13e errlogRemoveListeners() handle self-removal
Handle errlogRemoveListeners() during a callback.
2022-12-20 20:12:04 -08:00
Michael Davidsaver
7448a8bfa9 errlog: worker exit when buffer is empty
Allow the worker to flush any buffered messages.
After pvt.atExit, further logging will be synchronous.
Ensure any flusher gets a final wakeup.
2022-12-20 20:12:04 -08:00
Michael Davidsaver
166267a32f ringPointerTest in thread 2022-12-20 20:12:04 -08:00
Andrew Johnson
b460c2659e Fix for GH issue #219, menu fields with non-choice values
It's rare, but menu fields may hold a value that does not correspond
to one of the menu choices; the default value of the SSCN fields is
the most common example (65535). Change the type conversion routines
to return a numeric string instead of giving an error.

DBF_DEVICE was fixed in dbFastLinkConv.c before the 7.0.7 release.
2022-12-20 20:12:04 -08:00
Michael Skoufis
7ccc3ab82d Modify pointer type in example to match options from epicsTypes.h 2022-12-20 20:12:04 -08:00
Michael Davidsaver
adb0c898a6 doc 2022-12-20 20:12:04 -08:00
Michael Davidsaver
b38ff09f6e Com: iocsh: Tab completion of variable names for "var" 2022-12-20 20:12:04 -08:00
Michael Davidsaver
d9ca8a70f0 Com: iocsh: Tab completion
Add tab completion for "help ...", record names, and "pdbbase"
2022-12-20 20:12:04 -08:00
Michael Davidsaver
60128ee924 Com: separate iocsh argument splitting 2022-12-20 20:12:04 -08:00
Michael Davidsaver
b189991f9d Com: iocsh: add tab completion for command names 2022-12-20 20:12:04 -08:00
Michael Davidsaver
07ffc1ffae Com: introduce epicsReadlinePvt.h
Helping IDEs understand gnuReadline.c and osdReadline.c
2022-12-20 20:11:45 -08:00
Michael Davidsaver
a6afef4850 calc: add FMOD 2022-12-20 20:11:44 -08:00
Michael Davidsaver
9c0c486111 appveyor: bypass auto-detect of readline 2022-12-08 09:12:45 -08:00
Michael Davidsaver
3f5cf61fb6 update pvData 2022-12-05 10:51:47 -08:00
Andrew Johnson
6222902688 Update pva2pva module 2022-12-01 12:43:18 -06:00
Andrew Johnson
7febee04fa More & better Perl script dependencies 2022-11-30 13:35:56 -06:00
Andrew Johnson
4640f0a8ae Fix dbdExpand.pl issue 2022-11-30 13:34:22 -06:00
Andrew Johnson
8969a952e4 configure: Make Perl-generated files dependent on their .pl script 2022-11-24 17:57:05 -06:00
Michael Davidsaver
bc9415bb10 epicsErrlogTest: osiSockAttach() 2022-11-15 10:07:26 -08:00
Andrew Johnson
cbd86ada20 regRecDevDrv.pl: Don't break long symbol names in DBD files
Makes a test symbol name long enough to trigger the problem.

Fixes lp: #1995728
2022-11-04 11:09:06 -05:00
Michael Davidsaver
0f8ea3aa36 doc update dbUnitTest.h 2022-09-25 09:34:13 -07:00
Andrew Johnson
f62f68fd66 Update all submodules for future development 2022-09-07 17:00:45 -05:00
Andrew Johnson
eddafd2827 Release Checklist used for 7.0.7 2022-09-07 16:58:21 -05:00
Andrew Johnson
e3ce9d7f1a Adjustments to the make-tar.sh script 2022-09-07 16:57:09 -05:00
Andrew Johnson
df96e6df0f Set next development versions 2022-09-07 13:54:12 -05:00
Joao Paulo Martins
6c573b496a longout rec: fix behaviour when record is processed for the first time and OOPT is On Change 2021-03-10 18:50:42 +01:00
Joao Paulo Martins
1d85bc7424 longout record: detect OUT link change using special function AFTER put, better documentation 2021-03-10 11:07:30 +01:00
Joao Paulo Martins
f4d94b9725 Longout OOPT field refactoring and updated documentation; Release notes additions 2021-03-09 16:31:54 +01:00
Joao Paulo Martins
4e7a18bfb4 Adding test routines for longout record with OOPT field 2021-03-09 16:31:54 +01:00
Joao Paulo Martins
c1ae5064b8 Added OOPT to longout record 2021-03-09 16:31:54 +01:00
149 changed files with 3819 additions and 1079 deletions

View File

@@ -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:

View File

@@ -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

Submodule .ci updated: 8a2666a9de...1e0e326f74

8
.editorconfig Normal file
View 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

View 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

View File

@@ -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
View File

@@ -11,6 +11,7 @@
/modules/Makefile.local
O.*/
/QtC-*
/.qtc_*
/.vscode/
*.orig
*.log

View File

@@ -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' \

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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))

View File

@@ -5,3 +5,4 @@
#Include definitions common to unix hosts
include $(CONFIG)/os/CONFIG.UnixCommon.Common
CMPLR_CLASS = clang

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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.

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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/&lt;module&gt;/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' &lt;module-version&gt;
git tag -m 'ANJ: Tag for EPICS 7.0.7' &lt;module-version&gt;
</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>

View File

@@ -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>
*/

View File

@@ -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] );

View File

@@ -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"

View File

@@ -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};

View File

@@ -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 $@) $@

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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 */

View File

@@ -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;

View File

@@ -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 */

View File

@@ -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);

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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");

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View 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;
}
}

View File

@@ -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");
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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"})

View File

@@ -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];
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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;

View File

@@ -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)

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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)

View File

@@ -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;

View File

@@ -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)

View File

@@ -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.

View File

@@ -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

View File

@@ -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);

View File

@@ -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_]';

View File

@@ -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)

View File

@@ -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")

View File

@@ -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);

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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)")
}

View 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();
}

View 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")
}

View File

@@ -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);

View 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();
}

View File

@@ -0,0 +1,4 @@
record(sub, "InvalidINPARec") {
field(SNAM, "subproc")
field(INPA, "nonexistent")
}

View File

@@ -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

View File

@@ -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

View File

@@ -29,6 +29,8 @@
#include "postfix.h"
#include "asLib.h"
#undef ECHO /* from termios.h */
int asCheckClientIP;
static epicsMutexId asLock;

View File

@@ -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);

View File

@@ -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