Compare commits

...

73 Commits

Author SHA1 Message Date
20dde4c1d8 typo
Some checks failed
StreamDevice / Native Linux with 3.14 (push) Has been cancelled
StreamDevice / Native Linux with 3.15 (push) Has been cancelled
StreamDevice / Native Linux with clang (push) Has been cancelled
StreamDevice / OSX (push) Has been cancelled
StreamDevice / Native Linux (WError) (push) Has been cancelled
StreamDevice / Cross mingw64 DLL (push) Has been cancelled
StreamDevice / RTEMS 4.10 (push) Has been cancelled
StreamDevice / Cross mingw64 static (push) Has been cancelled
StreamDevice / vs2019 DLL (push) Has been cancelled
StreamDevice / vs2019 static (push) Has been cancelled
2025-05-27 17:09:16 +02:00
f61e6404f5 checksums after regsub must check original input 2025-05-27 16:57:58 +02:00
9080d6ca8e typo fixed 2025-05-23 15:28:52 +02:00
13e7f2d3dc Merge pull request #95 from henriquesimoes/scalcout-doc
Document stream-base.dbd usage when sCalcout is not needed
2025-05-22 18:42:22 +02:00
e87e093c84 in recent asyn versions vxi11 is optional 2025-05-06 14:48:59 +02:00
7debc86514 added checksum for Spellman High Voltage Supplies MPS series 2025-01-10 15:47:22 +01:00
668d1d5255 add some per-record debugging 2024-06-06 13:49:15 +02:00
ae5ca0c45b allow to use the StreamDebugClass without file name or line number 2024-06-06 11:29:09 +02:00
b00099973f don't need the wrapper function 2024-06-06 11:29:09 +02:00
5bf5cb9a67 example terminal.tcl uses LF terminator 2024-06-06 11:29:09 +02:00
f6848f0503 Update .ci 2024-03-20 10:17:54 +01:00
1496089bc8 Bugfix: make sure not to lose teminating 0 byte in StreamBuffer::replace() 2024-03-19 09:38:11 +01:00
a090cd4d8f fix header file path 2024-03-18 17:43:45 +01:00
2b3e4189c1 Document stream-base.dbd usage when scalcout is not needed
Having StreamDevice built with scalcout support does not require that
the application also depend on calc if it does not use this record
type. But this is only true if we load `stream-base.dbd` instead of
`stream.dbd`. Therefore, this alternative database definition should be
documented in the setup page.
2023-06-26 10:43:59 -03:00
211f689cdf decuments recently added checksums 2022-11-21 17:34:20 +01:00
793675bb12 fix leybold sum 2022-11-21 17:11:42 +01:00
922294bf6a strncasecmp is still not reliably available in Vxworks 6 2022-10-07 12:20:23 +02:00
942c4779c9 add GHA config 2022-10-05 14:25:08 +02:00
8ceee295ae Removed hardcode CHECKRELEASE in configure/Makefile 2022-10-05 14:25:08 +02:00
c30e2a4e31 vxWorks 6.9 has stdint.h and strncasecmp, RTEMS 5 has strncasecmp 2022-10-05 14:24:34 +02:00
fc67fb8721 Fix cplusplus linkage error with extern C 2022-07-11 15:44:16 +02:00
c123c5c8f7 Quiet warning with -Wformat-security
Best practice is to only use constant format strings,
which ansiEscape() almost is.
2022-07-11 15:43:26 +02:00
8746dea7cf fix HTML example to use correct line terminator CR LF 2022-07-11 15:43:26 +02:00
2915830b02 fix some rendering problems when printing 2022-07-11 15:43:26 +02:00
fdfa4d4695 fix makepdf for newer wkhtmltopdf version 2022-07-11 15:43:26 +02:00
4d717288da fix compile problem with EPICS 3.13 on vxWorks 6 2022-07-11 15:43:26 +02:00
94721c2b0e fix crash due to length underflow 2021-11-11 11:49:32 +01:00
bf0e755913 add new checksum: bitsum 2021-11-11 11:49:32 +01:00
51e4a0749d make "ifdef CALC" also consider SYNAPPS variable
change the ifdefs to the same expression used in CONFIG_STREAM
2021-11-05 11:42:48 +01:00
4cdace3ffe make "stream" in DBD and other file names explicit
The LIBRARY_* variables may specify *multiple* libraries.
(So they can't be used as part of file names.)
2021-11-05 11:42:48 +01:00
d19b16d096 fix strncpy warning 2021-11-05 11:21:21 +01:00
b1e0d63c6b Canonical handling of user include files 2021-11-05 10:14:21 +01:00
d1b43b879c strncasecmp does exist when compiling for Windows on MinGW 2021-11-05 10:13:58 +01:00
6b0ee5e946 Conditionally use tirpc if defined 2021-09-21 13:01:32 +01:00
a0d1b35862 Add preprocessor definition for static PCRE 2021-09-21 12:58:20 +01:00
dfbd308d46 32 bit Windows needs wrapper for calling convention reasons 2021-09-01 10:13:58 +02:00
b615dae9b1 Change log added 2021-07-05 19:57:03 +02:00
ac6c96c645 poll for I/O Intr only while interruptAccept is true 2021-07-05 19:10:46 +02:00
fe1ac364ab allow to use absolute file paths 2021-07-05 19:09:58 +02:00
a06006eade allow : and ; as separators in STREAM_PROTOCOL_PATH 2021-07-05 19:09:58 +02:00
a4e843cc4c re-arrange includes: first OS, then other software (e.g. EPICS), then my own headers 2021-07-05 19:09:57 +02:00
601d4a3709 shifted noisy debug from parser and I/O Intr to level 2 2021-07-05 19:09:48 +02:00
f495dd9853 print thread name in messages even if time stamps are not printed 2021-07-05 19:09:10 +02:00
6b05e006da fix initial space when timestamp is off 2021-07-05 19:09:10 +02:00
068632326c document streamMsgTimeStamped 2021-07-05 19:09:10 +02:00
a5eb4618b7 added a variable 'streamMsgTimeStamped' to enable/disable timestamps on debug & error messages 2021-07-05 19:09:09 +02:00
fe7f4a5e1b some rules for contribution added 2021-07-05 19:09:09 +02:00
0010b8f23f remove signed/unsigned warning 2021-07-05 19:09:08 +02:00
7e42f6fddf fix initializer which is not compatible with vxWorks 5 2021-07-05 19:09:08 +02:00
2b15ae7ac0 Refactor to set dead time as a shell variable and add documentation 2021-07-05 19:09:08 +02:00
055e141791 Added configurable dead time for repeated messages 2021-07-05 19:09:07 +02:00
52486ae7a0 Created a generic function for reducing logging frequency of the same error 2021-07-05 19:09:07 +02:00
becf7d5585 Print reply timeout errors only every 5 seconds 2021-07-05 19:09:07 +02:00
132f03fc6c Reply timeouts will now not be printed multiple times 2021-07-05 19:09:06 +02:00
3978357f17 fix test after introducing ansiEscape() function 2021-07-05 19:09:06 +02:00
a1ec9b99e8 color mode depends on stderr, not stdout 2021-07-05 19:09:05 +02:00
377d511c67 Merge branch 'lrc_checksum' of https://github.com/marciodo/StreamDevice 2021-04-09 17:41:57 +02:00
849586b7fa Merge branch 'issue_65' of https://github.com/krisztianloki/StreamDevice 2021-04-09 17:37:28 +02:00
e6b2944c67 build for all OS classes 2021-04-01 14:47:36 +02:00
67c205e87b Added description of the LRC checksum to the documentation 2021-03-17 14:24:32 -07:00
575c0ffcf8 Implemented lrc checksum and also a version that interprets ASCII as number: hexlrc 2021-03-16 19:14:36 -07:00
e662ebda04 Initialize inTerminatorDefined and outTerminatorDefined 2021-03-02 15:47:29 +01:00
ea8873becd Remove out of date comment 2021-02-25 15:43:29 +00:00
cf7e7bd6ee Add <unistd.h> for non-windows systems 2021-02-23 17:15:27 +00:00
e36ee60ba7 Remove errlog.h include 2021-02-23 15:26:50 +00:00
e7f36a71af Use streamDebugColored IOC variable instead 2021-02-23 15:24:24 +00:00
ce4b14c611 Check for NULL as well as INVALID_HANDLE_VALUE 2021-02-23 01:09:50 +00:00
7c55d7bdfa Add missing space character 2021-02-23 01:00:28 +00:00
6afa4828eb Allow configuring of coloured console output with STREAM_DEBUG_COLOR
The STREAM_DEBUG_COLOR environment variable can be set to:
    yes  - always generate ANSI colour escape codes on debug/error output
    no   - use plain text output for debug/error
    auto - (default) output ANSI codes if terminal supports it
2021-02-22 22:07:52 +00:00
75bbb1a252 vxWorks does not support %hhx scanf format 2020-12-11 11:42:55 +01:00
227bb83f60 fix formats for vxWorks 6.9 2020-09-29 17:40:04 +02:00
4edff374d0 fix some type conversion warning on Windows 2020-09-29 17:39:07 +02:00
9273476135 install missing headers 2020-09-29 17:34:58 +02:00
48 changed files with 1685 additions and 518 deletions

1
.ci Submodule

Submodule .ci added at dead44c3cb

12
.ci-local/defaults.set Normal file
View File

@ -0,0 +1,12 @@
MODULES=calc asyn
# EPICS Base
BASE_DIRNAME=base
BASE_REPONAME=epics-base
BASE_REPOOWNER=epics-base
BASE_VARNAME=EPICS_BASE
BASE_RECURSIVE=no
ASYN_REPOOWNER=epics-modules
CALC_REPOOWNER=epics-modules

164
.github/workflows/ci-scripts-build.yml vendored Normal file
View File

@ -0,0 +1,164 @@
# .github/workflows/ci-scripts-build.yml for use with EPICS Base ci-scripts
# (see: https://github.com/epics-base/ci-scripts)
# This is YAML - indentation levels are crucial
# Workflow name, shared by all branches
name: StreamDevice
# Trigger on pushes and PRs to any branch
on:
push:
paths-ignore:
- 'docs/*'
- '.gitattributes'
- '.gitignore'
- '**/*.html'
- '**/*.md'
pull_request:
paths-ignore:
- 'docs/*'
- '.gitattributes'
- '.gitignore'
- '**/*.html'
- '**/*.md'
env:
SETUP_PATH: .ci-local:.ci
EPICS_TEST_IMPRECISE_TIMING: YES
jobs:
native:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
# Set environment variables from matrix parameters
env:
CMP: ${{ matrix.cmp }}
BCFG: ${{ matrix.configuration }}
BASE: ${{ matrix.base }}
WINE: ${{ matrix.wine }}
RTEMS: ${{ matrix.rtems }}
RTEMS_TARGET: ${{ matrix.rtems_target }}
TEST: ${{ matrix.test }}
EXTRA: ${{ matrix.extra }}
VV: "1"
strategy:
fail-fast: false
matrix:
include:
- name: Native Linux (WError)
os: ubuntu-20.04
cmp: gcc
configuration: default
base: "7.0"
extra: "CMD_CPPFLAGS=-Werror"
pcre: apt
- name: Cross mingw64 DLL
os: ubuntu-20.04
cmp: gcc
configuration: default
base: "7.0"
wine: "64"
pcre: no
- name: Cross mingw64 static
os: ubuntu-20.04
cmp: gcc
configuration: static
base: "7.0"
wine: "64"
pcre: no
- name: RTEMS 4.10
os: ubuntu-20.04
cmp: gcc
configuration: default
base: "7.0"
rtems: "4.10"
rtems_target: RTEMS-pc386-qemu
pcre: no
- name: Native Linux with clang
os: ubuntu-20.04
cmp: clang
configuration: default
base: "7.0"
pcre: apt
- name: Native Linux with 3.15
os: ubuntu-20.04
cmp: gcc
configuration: default
base: "3.15"
pcre: apt
- name: Native Linux with 3.14
os: ubuntu-20.04
cmp: gcc
configuration: default
base: "3.14"
pcre: apt
- name: OSX
os: macos-latest
cmp: clang
configuration: default
base: "7.0"
pcre: no
- name: vs2019 DLL
os: windows-2019
cmp: vs2019
configuration: debug
base: "7.0"
pcre: no
extra: "CMD_CFLAGS=-analysis CMD_CXXFLAGS=-analysis"
- name: vs2019 static
os: windows-2019
cmp: vs2019
configuration: static-debug
base: "7.0"
pcre: no
extra: "CMD_CFLAGS=-analysis CMD_CXXFLAGS=-analysis"
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Automatic core dumper analysis
uses: mdavidsaver/ci-core-dumper@master
- name: "apt-get install"
run: |
sudo apt-get update
sudo apt-get -y install qemu-system-x86 g++-mingw-w64-x86-64 gdb
if: runner.os == 'Linux'
- name: Reset RELEASE
shell: bash
# 'make' on Mac doesn't understand "undefine PCRE"
# so replace the whole file
run: |
cat <<EOF > configure/RELEASE
-include \$(TOP)/../RELEASE.local
-include \$(TOP)/../RELEASE.\$(EPICS_HOST_ARCH).local
-include \$(TOP)/configure/RELEASE.local
EOF
- name: Prepare and compile dependencies
run: python .ci/cue.py prepare
- name: "apt-get install pcre"
if: matrix.pcre == 'apt'
shell: bash
run: |
sudo apt-get -y install libpcre3-dev
cat <<EOF >> configure/CONFIG_SITE.local
PCRE_INCLUDE=/usr/include
PCRE_LIB=/usr/lib
EOF
- name: Build main module
run: python .ci/cue.py build

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule ".ci"]
path = .ci
url = https://github.com/epics-base/ci-scripts.git

303
CHANGELOG.md Normal file
View File

@ -0,0 +1,303 @@
# Changelog
## Changes in release 2.8.20
Fix missing initialization of `inTerminator` and `outTerminator`.
Thanks to Krisztián Löki.
New checksums `%<lrc>` and `%<hexlrc>`.
Thanks to Marcio Paduan Donadio.
Make timestamp output in error and debug messages optional with new
iocsh variable `streamMsgTimeStamped`. Default is 1.
Reduce number of duplicate error messages. Dead time for repeated messages
is configurable with new iocsh variable `streamErrorDeadTime`.
Thanks to Dominic Oram.
Shifted some noisy debug messages to `streamDebug` level 2.
In `STREAM_PROTOCOL_PATH`, allow both `:` and `;` separators on all
operating systems to make startup scripts OS independent.
On Windows however, an initial single letter followed by a `:` is detected
as a drive letter. Thus single letter directories must be separated with
`;` or followed by `\` or `/` on Windows.
Allow absolute path protocol files. Those will not use
`STREAM_PROTOCOL_PATH`.
Changed the default search path from `"."` to `NULL`.
Only poll for `I/O Intr` records while `interruptAccept` is true,
in particular not between `iocPause` and `iocRun`.
Added this change log and a document about contributing to StreamDevice.
## Changes in release 2.8.19
Make colorization of error and debug messages optional with new
iocsh variable `streamDebugColored`. Defaults to 1 when output is a tty
and 0 otherwise.
## Changes in release 2.8.18
Another format string fix for VxWorks in checksum converter.
## Changes in release 2.8.17
Fix format strings for VxWorks 6.9 in checksum converter.
Fix some compiler warnings on Windows.
Fix some missing header files installations.
## Changes in release 2.8.16
Drop support of upper level `configure/` directory for improved
compatibility with SynApps.
Fix problem with extra spaces after protocol name in record links.
Make version string generation more robust.
## Changes in release 2.8.15
Do not overwrite `HOST_OPT` flag.
Fix version string generation for zip downloads.
On Windows use `;` and `\` in `STREAM_PROTOCOL_PATH`.
(Was not the case before due to typo in macro name.)
Link streamApp with `seq` and `pv` libraries when `SNCSEQ` is defined in
RELEASE file.
## Changes in release 2.8.14
Fix version string generation from git tags.
## Changes in release 2.8.13
Fix bug with integer datatype handling in redirectons.
## Changes in release 2.8.12
Apply GPL-3.0.
## Changes in release 2.8.11
Fix buffer overrun.
Thanks to Garth Brown and Bruce Hill.
Fix bug in 64 bit data type handling in array records (`aai`, `aao`,
`waveform`).
Thanks to Andrew Johnson.
Fix Windows linker problem.
Thanks to Freddie Akeroyd.
Allow `(,)` inside protocol parameters. Limitation: Parenthesis must be in
matching pairs. Commas within inner parenthesis are consideres part of the
argument. Example: `protocol(arg1, (arg2, still arg 2), arg3)`.
New checksums `%<brksCryo>`, `%<xor8ff>`, `%<nsum8>`, `%<nsum16>`,
`%<nsum32>`, `%<notsum>`.
Thanks to Mark Davis.
Some error message cleanup.
## Changes in release 2.8.10
Fixes for Windows / Visual Studio 2010.
Thanks to Freddie Akeroyd.
Fix infinite loop in `I/O Intr` scanned records.
Enable error messages by default and always show error messages too when
debug messages are enabled.
Make sure any console output from StreamDevice can be redirected.
Fix some problems when building for old EPICS R3.13.
## Changes in release 2.8.9
Leave `asynTraceMask` alone. Before, streamDevice had set `setTraceMask`
to 0 initially.
Link with `sscan` module if `SSCAN` is defined in RELEASE file because the
`calc` module may need it.
Fix in regsub converter: Avoid infinite loop if an empty string is matched.
Add support for toupper/tolower to regsub converter.
Fix build problem observed on MacOS related to clash between `wait()`
system function and the StreamDevice `wait` command.
Allow `%%` in addition to `\%` to escape `%` in protocols (compatibility
with `printf`/`scanf`).
Have separate dbd file for applications without scalcout support built in.
Improved compatibility with SynApps. (fix of the attempt in 2.8.6)
## Changes in release 2.8.8
Fix problem with `in` command hanging forever if it is the the first
command of the protocol and the device is offline.
## Changes in release 2.8.7
Enable colored error/debug output on Windows.
Changed error/debug hex output highlight from fat to inverse.
Fix build problem on Windows.
## Changes in release 2.8.6
Improved compatibility with SynApps.
Fix redirection to records with names not starting with a letter or
underscore.
### Fixes/improvements for `mbbo` device support:
Use `.MASK` and `.SHFT` fields even if no `xxVL` field is defined
(and thus `.VAL` is used instead of `.RVAL`).
Bugfix: Set `mbbo` to unknown state `0xffff` if in readback no state
matches.
Bugfix: Use defined state alarms when `mbbo` is updated by `@init` handler.
## Changes in release 2.8.5
A `"\?"` at the end of an input format now matches an empty string.
Used to do so unintendedly in earlier versions but had been changed.
## Changes in release 2.8.4
Bugfix: After `@init` had been triggered with "magic value" 2 in `.PROC`
field, reset the field to 0.
## Changes in release 2.8.3
Increase limits on filename, protocolname, busname in record links.
Bugfix: Fix typo calcout device support that prevented compiling it.
## Changes in release 2.8.2
Bugfix: Fix string termination reading into char array records (`waveform`,
`aai`, `aao`, `lsi`, `lso`).
## Changes in release 2.8.1
Allow empty parameter list `()` in record links.
## Changes between 2.7 and 2.8.0
### Build system
Drop support for (buggy) cygnus-2.7.2 gcc (as used in some old cygwin
version).
Add standard EPICS App build system (`configure/`).
Some re-ordering of directories.
When upgrading from 2.7 or earlier to 2.8, better start with a fresh
source directory to avoid problems with files that have moved.
### New Features
Support for 64 bit integers. This affects some conversions (e.g. in `ai`
and `ao` records).
Device support for new record types `int64in`, `int64out`, `lsi` and `lso`
added.
Support for new `INT64` and `UINT64` types in `aai`, `aao` and `waveform`
records.
Allow spaces in protocol parameter list in record link.
One space after the opening `(`, before and after the separating `,` and
before the closing `)` ignored. More spaces are part of the parameter.
String format `%s` can now pad with 0-bytes instead of spaces using the
`0` flag.
New checksums `%<cpi>` and `%<leybold>`.
Properly distinguish beween signed and unsigned integers, e.g. when
converting to double.
Allow signed enums to do this: `%#{backwards=-1|stop=0|forwards=1}`.
#### New connection handling
Run `@init` handler each time the device (re-)connects
(as long as asynDriver detects connection changes), also at each `iocRun`
(after being paused with `iocPause`), and after each `streamReload`.
Also run `@init` of a record when the "magic value" 2 is written to the
`.PROC` field or when new `streamReinitRecord` shell function is called.
Trigger monitors when `@init` updates output records (after the intial run
in `iocInit`).
Use `"COMM"` error code in `.STAT` when device is disconnected.
If write fails because device has disconnected, try to re-connect and re-do
write once.
### Bug fixes
Fix parser bug when command reference was followed by `}` without `;`.
Fix potential buffer overrun during `streamReload`.
After `streamReload`, properly delete handlers and variables that do not
exist any longer.
Fix for waveforms of unsigned integers.
Several Windows related fixes and support building shared libraries on
Windows.
Fix C++11 warnings.
Check formatting of `exec` command for errors.
Fix bug in `dbior` output.
Fix BCD `%D` format and allow negative values.
Fix signed hex and octal formats.
Fix in regsub: Do not run regsub again on substituted string.
### Shell functions
New functions `streamReportRecord` and `streamSetLogfile`.
Support output redirection to file of stream shell functions
and protocol dump via `dbior`.
### Error and debug output
Cleanup some debug and error messages.
Hex bytes in debug/error output are now highlighted.
`StreamError` can now be set before `iocInit`.
Avoid repeating error messages.
Thanks to Ben Franksen.
### Documentation
HTML files converted to HTML 5.
Update and improve several chapters.
## Changes in releases up to 2.7.14
Not available. See git log.

81
Contributing.md Normal file
View File

@ -0,0 +1,81 @@
# Contributing to StreamDevice
Contributions from the EPICS community (and others) are welcome.
To ease the integration process, please follow the following guidelines.
All contributions should be done as a pull request to the git repository
https://github.com/paulscherrerinstitute/StreamDevice. Make sure to provide
meaningful commit messages (no essay but more than "changes").
For small modifications, a patch file is sufficient. Send it to me or better
create an issue on https://github.com/paulscherrerinstitute/StreamDevice/issues
and attach the patch file.
Justify your change requests. Write a short summary for your pull request
to explain what the change is about and what it improves or which bug it
fixes. Use the issue system on github to report bugs.
I reserve the right to accept or reject contributions or to request
modifications before I accept them.
Of course, you may as well report bugs without providing a solution yourself.
## Code compatibility
All code must compile for any EPICS release from at least R3.14.12 up to the
latest one. Likewise the code must be compatible with any operating system
supported by EPICS, like Linux, Windows, MacOS, RTEMS and VxWorks.
In particular VxWorks 5 compatibility rules out many modern C++ features.
But there are also other platforms that for example do not support C++11, so
don't use it.
There should also be no compiler warning on any OS.
Avoid compiler dependent features like #pragmas, assumptions on byte order,
type size (in particular the size of pointers and long int) and other
non-portable things like the availability of certain header files. Make sure
non-portable code parts are enclosed in proper compiler branches and provide
working implementations for all architectures.
Make sure that the code in AsynDriverInterface stays compatible with old
and new versions of asyn driver.
The core of StreamDevice does not depend on EPICS. This is on purpose, to be
able to use it in other control system frameworks. Modifications should not
add EPICS dependencies except to StreamEpics and AsynDriverInterface, or the
new dependency must be in a separate file which can be left out of the build
without jeopardizing the main functionality of StreamDevice.
The code must not depend on external libraries that may not be available on
all systems, except if provided as a separate file which can be left out of
the build on platforms that do not support the library.
## Language
Write in English. That includes all identifiers (variables, functions, ...),
comments, documentation and commit messages. Check your spelling.
## File formats
All files are in Unix format (\n line terminators). Do not change them to
any other format (e.g. Windows with \r\n terminators). Do not add new files in
other formats.
The files must contain only ASCII characters. Do not use any Unicode multi-byte
characters (including byte order marks) or any pre-Unicode code page dependent
characters (e.g. umlaut), not even in comments. Do not use form feed, vertical
tab, or other control characters except newline.
Indents are 4 spaces. Do not use tabs (except in Makefiles). Make sure your
editor is set up accordingly.
Files must end in a newline and there must be no spaces at the end of lines.
Do not add excessive amount of newlines at the end of files.
Do not add any editor configurations (e.g. for emacs) to the files. Also do
not add any configuration files or directories for development environments,
editors, etc.
-------
Dirk Zimoch <dirk.zimoch@psi.ch>, June 2021

View File

@ -5,7 +5,7 @@ else
include /ioc/tools/driver.makefile include /ioc/tools/driver.makefile
EXCLUDE_VERSIONS = 3.13.2 EXCLUDE_VERSIONS = 3.13.2
PROJECT=stream PROJECT=stream
BUILDCLASSES += Linux BUILDCLASSES += vxWorks Linux WIN32
DOCUDIR = docs DOCUDIR = docs
@ -24,7 +24,11 @@ HEADERS += src/StreamFormat.h
HEADERS += src/StreamFormatConverter.h HEADERS += src/StreamFormatConverter.h
HEADERS += src/StreamBuffer.h HEADERS += src/StreamBuffer.h
HEADERS += src/StreamError.h HEADERS += src/StreamError.h
HEADERS += src/StreamVersion.h HEADERS += src/StreamProtocol.h
HEADERS += src/StreamBusInterface.h
HEADERS += src/StreamCore.h
HEADERS += src/MacroMagic.h
HEADERS += $(COMMON_DIR)/StreamVersion.h
CPPFLAGS += -DSTREAM_INTERNAL -I$(COMMON_DIR) CPPFLAGS += -DSTREAM_INTERNAL -I$(COMMON_DIR)

View File

@ -1,14 +1,29 @@
#CONFIG # CONFIG - Load build configuration data
include $(TOP)/configure/CONFIG_APP #
# Add any changes to make definitions here # Do not make changes to this file!
#CROSS_COMPILER_TARGET_ARCHS = vxWorks-68040 # Allow user to override where the build rules come from
#CROSS_COMPILER_TARGET_ARCHS = RULES = $(EPICS_BASE)
# Use this when your IOC and the host use different paths # RELEASE files point to other application tops
# to access the application. Typically this will be include $(TOP)/configure/RELEASE
# used with the Microsoft FTP server or with NFS mounts. Use -include $(TOP)/configure/RELEASE.$(EPICS_HOST_ARCH)
# is indicated by failure of the cdCommands script on -include $(TOP)/configure/RELEASE.$(EPICS_HOST_ARCH).Common
# vxWorks. You must rebuild in the iocBoot directory ifdef T_A
# before this takes effect. -include $(TOP)/configure/RELEASE.Common.$(T_A)
#IOCS_APPL_TOP = <the top of the application as seen by the IOC> -include $(TOP)/configure/RELEASE.$(EPICS_HOST_ARCH).$(T_A)
endif
CONFIG = $(RULES)/configure
include $(CONFIG)/CONFIG
# Override the Base definition:
INSTALL_LOCATION = $(TOP)
# CONFIG_SITE files contain other build configuration settings
include $(TOP)/configure/CONFIG_SITE
-include $(TOP)/configure/CONFIG_SITE.$(EPICS_HOST_ARCH).Common
ifdef T_A
-include $(TOP)/configure/CONFIG_SITE.Common.$(T_A)
-include $(TOP)/configure/CONFIG_SITE.$(EPICS_HOST_ARCH).$(T_A)
endif

View File

@ -1,26 +0,0 @@
# CONFIG_APP
include $(TOP)/configure/RELEASE
-include $(TOP)/configure/RELEASE.$(EPICS_HOST_ARCH)
-include $(TOP)/configure/RELEASE.Common.$(T_A)
-include $(TOP)/configure/RELEASE.$(EPICS_HOST_ARCH).$(T_A)
ifneq ($(wildcard $(EPICS_BASE)/configure),)
CONFIG=$(EPICS_BASE)/configure
else
CONFIG=$(EPICS_BASE)/config
DIRS += config
endif
include $(CONFIG)/CONFIG
INSTALL_LOCATION = $(TOP)
ifdef INSTALL_LOCATION_APP
INSTALL_LOCATION = $(INSTALL_LOCATION_APP)
endif
ifdef T_A
-include $(TOP)/configure/O.$(T_A)/CONFIG_APP_INCLUDE
endif
# dbst based database optimization (default: NO)
DB_OPT = NO

40
configure/CONFIG_SITE Normal file
View File

@ -0,0 +1,40 @@
# CONFIG_SITE
-include $(SUPPORT)/configure/CONFIG_SITE
# Make any application-specific changes to the EPICS build
# configuration variables in this file.
#
# Host/target specific settings can be specified in files named
# CONFIG_SITE.$(EPICS_HOST_ARCH).Common
# CONFIG_SITE.Common.$(T_A)
# CONFIG_SITE.$(EPICS_HOST_ARCH).$(T_A)
# CHECK_RELEASE controls the consistency checking of the support
# applications pointed to by the RELEASE* files.
# Normally CHECK_RELEASE should be set to YES.
# Set CHECK_RELEASE to NO to disable checking completely.
# Set CHECK_RELEASE to WARN to perform consistency checking but
# continue building even if conflicts are found.
CHECK_RELEASE = YES
# Set this when you only want to compile this application
# for a subset of the cross-compiled target architectures
# that Base is built for.
#CROSS_COMPILER_TARGET_ARCHS = vxWorks-ppc32
# To install files into a location other than $(TOP) define
# INSTALL_LOCATION here.
#INSTALL_LOCATION=</absolute/path/to/install/top>
# Set this when the IOC and build host use different paths
# to the install location. This may be needed to boot from
# a Microsoft FTP server say, or on some NFS configurations.
#IOCS_APPL_TOP = </IOC's/absolute/path/to/install/top>
# These allow developers to override the CONFIG_SITE variable
# settings without having to modify the configure/CONFIG_SITE
# file itself.
-include $(TOP)/../CONFIG_SITE.local
-include $(TOP)/../configure/CONFIG_SITE.local
-include $(TOP)/configure/CONFIG_SITE.local

View File

@ -2,11 +2,7 @@
TOP=.. TOP=..
include $(TOP)/configure/CONFIG_APP include $(TOP)/configure/CONFIG
# Set the following to NO to disable consistency checking of
# the support applications defined in $(TOP)/configure/RELEASE
CHECK_RELEASE = YES
TARGETS = $(CONFIG_TARGETS) TARGETS = $(CONFIG_TARGETS)
CONFIGS += $(subst ../,,$(wildcard $(CONFIG_INSTALLS))) CONFIGS += $(subst ../,,$(wildcard $(CONFIG_INSTALLS)))

View File

@ -485,6 +485,12 @@ than 9.
<p> <p>
This is not a normal "converter", because no user data is converted. This is not a normal "converter", because no user data is converted.
Instead, a checksum is calculated from the input or output. Instead, a checksum is calculated from the input or output.
<span class="new">
Any pre-processing of input, e.g. by the <a href="#regsub">regsub</a> converter
is ignored for the calculation of the checksum.
</span>
</p>
<p>
The <em>width</em> field is the byte number from which to start The <em>width</em> field is the byte number from which to start
calculating the checksum. calculating the checksum.
Default is 0, i.e. the first byte of the input or output of the current Default is 0, i.e. the first byte of the input or output of the current
@ -589,6 +595,23 @@ In input, the next byte or bytes must match the checksum.
href="http://www.ietf.org/rfc/rfc1950.txt">RFC 1950</a>.</dd> href="http://www.ietf.org/rfc/rfc1950.txt">RFC 1950</a>.</dd>
<dt><code>%&lt;hexsum8&gt;</code></dt> <dt><code>%&lt;hexsum8&gt;</code></dt>
<dd>One byte. The sum of all hex digits. (Other characters are ignored.)</dd> <dd>One byte. The sum of all hex digits. (Other characters are ignored.)</dd>
<dt><code>%&lt;lrc&gt;</code></dt>
<dd>One byte. The Longitudinal Redundancy Check according to <a target="ex"
href="https://en.wikipedia.org/wiki/Longitudinal_redundancy_check">Wikipedia</a>.</dd>
<dt><code>%&lt;hexlrc&gt;</code></dt>
<dd>One byte. The LRC for the hex digits. (Other characters are ignored.)</dd>
<dt><code>%&lt;leybold&gt;</code></dt>
<dd>One byte. Used by some Leybold products. 255-bytesum%255 (+32 if result would be <32)</dd>
<dt><code>%&lt;brksCryo&gt;</code></dt>
<dd>One byte. Used by Brooks Cryopumps.</dd>
<dt><code>%&lt;CPI&gt;</code></dt>
<dd>One byte. Used by TRIUMF CPI RF amplifier.</dd>
<dt><code>%&lt;bitsum&gt;</code> or <code>%&lt;bitsum8&gt;</code></dt>
<dd>One byte. Number of 1 bits in all characters.</dd>
<dt><code>%&lt;bitsum16&gt;</code></dt>
<dd>Two bytes. Number of 1 bits in all characters.</dd>
<dt><code>%&lt;bitsum32&gt;</code></dt>
<dd>Four bytes. Number of 1 bits in all characters.</dd>
</dl> </dl>
<a name="regex"></a> <a name="regex"></a>
@ -694,10 +717,14 @@ However if an empty string is matched, searching advances by 1 character in orde
avoid matching the same empty string again.</span> avoid matching the same empty string again.</span>
</p> </p>
<p> <p>
In input this converter pre-processes data received from the device before In input, this converter pre-processes data received from the device before
following converters read it. following converters read it.
Converters preceding this one will read unmodified input. Converters preceding this one will read unmodified input.
Thus place this converter before those whose input should be pre-processed. Thus place this converter before those whose input should be pre-processed.
<span class="new">
However, <a href="#chksum">checksum</a> converters will always use the unmodified
input as sent by the device because the modified input would not match the checksum.
</span>
</p> </p>
<p> <p>
In output it post-processes data already formatted by preceding converters In output it post-processes data already formatted by preceding converters

View File

@ -1,10 +1,22 @@
#/bin/sh #/bin/sh
if ! wkhtmltopdf -V >/dev/null 2>&1 wkhtmltopdf --enable-local-file-access -V >/dev/null 2>&1
then case $? in
127)
echo "wkhtmltopdf not installed." >&2 echo "wkhtmltopdf not installed." >&2
echo "See https://wkhtmltopdf.org" >&2 echo "See https://wkhtmltopdf.org" >&2
exit 1 exit 1
fi ;;
0)
# have (and need) --enable-local-file-access
ENABLE_FILE_ACCESS=--enable-local-file-access
;;
1)
# have no (and need no) --enable-local-file-access
;;
*)
# Some error but I don't know what it means. Try anyway.
;;
esac
PAGES=" PAGES="
index.html index.html
@ -43,5 +55,5 @@ osinterface.html
" "
rm -f stream.pdf rm -f stream.pdf
wkhtmltopdf --print-media-type --dpi 1200 --zoom 0.85 --page-size Letter \ wkhtmltopdf --print-media-type --page-size Letter \
$PAGES stream.pdf $ENABLE_FILE_ACCESS $PAGES stream.pdf

View File

@ -85,7 +85,7 @@ Make sure that the <em>asyn</em> library can be found by adding the path to the
ASYN=/home/epics/asyn4-30 ASYN=/home/epics/asyn4-30
</pre> </pre>
<h4>Support for <em>sCalcout</em> record</h4> <h4 id="scalcout">Support for <em>sCalcout</em> record</h4>
<p> <p>
The <a The <a
href="https://htmlpreview.github.io/?https://raw.githubusercontent.com/epics-modules/calc/R3-6-1/documentation/sCalcoutRecord.html" href="https://htmlpreview.github.io/?https://raw.githubusercontent.com/epics-modules/calc/R3-6-1/documentation/sCalcoutRecord.html"
@ -109,7 +109,10 @@ modules. Release R2-8 or newer is recommended.
</p> </p>
<p> <p>
Support for the <em>sCalcout</em> is optional. <em>StreamDevice</em> works Support for the <em>sCalcout</em> is optional. <em>StreamDevice</em> works
as well without <em>sCalcout</em> or <em>SynApps</em>. as well without <em>sCalcout</em> or <em>SynApps</em>. If your application does
not need this record support, you may load <kbd>stream-base.dbd</kbd> instead of
<kbd>stream.dbd</kbd>, making it optional to include <em>calc</em> as an
application dependency.
</p> </p>
<h4>Support for regular expression matching</h4> <h4>Support for regular expression matching</h4>
@ -185,13 +188,14 @@ Regular expressions are optional. If you don't want them, you don't need this.
Go to the <em>StreamDevice</em> directory Go to the <em>StreamDevice</em> directory
and run <code>make</code> (or <code>gmake</code>). and run <code>make</code> (or <code>gmake</code>).
This will create and install the <em>stream</em> library and the This will create and install the <em>stream</em> library and the
<kbd>stream.dbd</kbd> file and an example IOC application. <em>StreamDevice</em> database definition files and an example IOC application.
</p> </p>
<p> <p>
To use <em>StreamDevice</em>, your own application must be built with the To use <em>StreamDevice</em>, your own application must be built with the
<em>stream</em> and <em>asyn</em> (and optionally <em>pcre</em>) libraries <em>stream</em> and <em>asyn</em> (and optionally <em>pcre</em>) libraries
and must load <kbd>asyn.dbd</kbd> and <kbd>stream.dbd</kbd>. and must load <kbd>asyn.dbd</kbd> and <kbd>stream.dbd</kbd> (or alternatively
<kbd>stream-base.dbd</kbd>; see <a href="#scalcout">Support for sCalcout record</a>).
</p> </p>
<p> <p>
Include the following lines in your application <kbd>Makefile</kbd>: Include the following lines in your application <kbd>Makefile</kbd>:
@ -385,25 +389,54 @@ See the <a href="protocol.html">next chapter</a> for protocol files in depth.
Generation of debug and error messages is controlled with two shell variables, Generation of debug and error messages is controlled with two shell variables,
<code>streamDebug</code> and <code>streamError</code>. <code>streamDebug</code> and <code>streamError</code>.
Setting those variables to 1 (actually to any number but 0) enables the Setting those variables to 1 (actually to any number but 0) enables the
messages. messages. A few noisy and rarely useful debug messages are only enabled when
setting <code>streamDebug</code> to 2.
Per default debug messages are switched off and error messages are switched on. Per default debug messages are switched off and error messages are switched on.
Errors occuring while loading protocol files are always shown. Errors occuring while loading protocol files are always shown.
</p> </p>
<p> <p>
Warning: Enabling debug messages can create a lot of output! Warning: Enabling debug messages this way can create a lot of output!
At the moment, there is no way to set filters on debug or error messages. Therefore, some limited debugging can be enabled per record, independent of
the <code>streamDebug</code> variable using the <code>.TPRO</code> field of
the record. Currently, setting <code>.TPRO</code> to 1 or 2 enables some
basic information about the processing of a record and its i/o.
</p> </p>
<p> <p>
Debug output can be redirected to a file with the command Debug output can be redirected to a file with the command
<code>streamSetLogfile("<var>filename</var>")</code>. <code>streamSetLogfile("<var>filename</var>")</code>.
When called without a filename, debug output is directed back If the file already exists, it will be overwritten, not appended to.
to the console. While debug messages are only written to the defined log file, error messages
are still printed to <var>stderr</var> too.
Calling <code>streamSetLogfile</code> without a filename directs debug output
back to <var>stderr</var> and closes the log file.
</p>
<p>
By default, error messages to the console are printed in red color if
<var>stderr</var> is a tty at startup time, using ANSI color codes. Some
terminals may not support this properly.
The variable <code>streamDebugColored</code> can be set to 0 or 1 to
disable or enable colored error messages explicitly.
Error messages written to a log file do not use colors.
</p>
<p>
Error and debug messages are prefixed with a time stamp unless the variable
<code>streamMsgTimeStamped</code> is set to 0.
</p>
<p>
when a device is unresponsive, StreamDevice may produce many repeated timeout
messages. To reduce this, you can set <code>streamErrorDeadTime</code>
to an integer number of seconds. In this case, repeated timeout messages
will not be printed during the specified dead time after the last printed
message. The default dead time is 0, resulting in every message being printed.
</p> </p>
<h3>Example (vxWorks):</h3> <h3>Example (vxWorks):</h3>
<pre> <pre>
streamError=1 streamError=1
streamDebug=1 streamDebug=1
streamDebugColored=1
streamErrorDeadTime=30
streamMsgTimeStamped=1
streamSetLogfile("logfile.txt") streamSetLogfile("logfile.txt")
</pre> </pre>
@ -411,6 +444,9 @@ streamSetLogfile("logfile.txt")
<pre> <pre>
var streamError 1 var streamError 1
var streamDebug 1 var streamDebug 1
var streamDebugColored 1
var streamErrorDeadTime 30
var streamMsgTimeStamped 1
streamSetLogfile("logfile.txt") streamSetLogfile("logfile.txt")
</pre> </pre>

View File

@ -1,90 +1,104 @@
a:link {color: #0000D0;} a:link { color: #0000D0; }
a:visited {color: #0000D0;} a:visited { color: #0000D0; }
a:hover {color: #FF0000;} a:hover { color: #FF0000; }
body { body {
margin-right:1em; margin-right: 1em;
margin-left:15em; margin-left: 15em;
margin-top:75px; margin-top: 75px;
padding-top:1px; padding-top: 1px;
font-family: Helvetica, Arial, sans-serif; font-family: Helvetica, Arial, sans-serif;
font-size: 100%; font-size: 100%;
background-color:#ffffff; background-color: #ffffff;
} }
a[name] { position:relative; top:-11ex;} a[name] {
position: relative;
top: -11ex;
}
pre, tt, kbd, code {
font-size: 95%;
font-family: Mono, "Lucida Console", Courier, monospace;
}
pre { pre {
background-color:#f4f4f4; background-color: #f4f4f4;
padding:1ex; padding: 1ex;
border:1px solid #000000; border: 1px solid #000000;
white-space:pre; white-space: pre;
margin:2ex; margin: 2ex;
page-break-inside:avoid; page-break-inside: avoid;
font-size: 85%;
white-space: pre-wrap;
} }
kbd { kbd {
font-weight:bold; font-weight: bold;
}
code {
color: #008000;
} }
dt { dt {
margin-top:0.5ex; margin-top: 0.5ex;
} }
h1 { h1 {
font-size:250%; font-size: 250%;
margin-top:0; margin-top: 0;
font-style:italic; font-style: italic;
font-weight:bold; font-weight: bold;
font-family:"Times New Roman", serif; font-family: "Times New Roman", Times, serif;
text-align:center; text-align: center;
position:fixed; position: fixed;
top:0; top: 0;
left:0; left: 0;
width:100%; width: 100%;
line-height:190%; line-height: 190%;
background-color:white; background-color: white;
border-width:0; border-width: 0;
border-bottom:3px solid #1b4486; border-bottom: 3px solid #1b4486;
white-space:nowrap; white-space: nowrap;
background-image:url(PSI.png); background-image: url(PSI.png);
background-repeat:no-repeat; background-repeat: no-repeat;
background-position:10px 5px; background-position: 10px 5px;
text-shadow:.1em .1em .1em darkgray; text-shadow: .1em .1em .1em lightgray;
box-shadow:0 .3em .1em -.2em darkgray; box-shadow: 0 .3em .1em -.2em darkgray;
} }
h2 { h2 {
font-size:150%; font-size: 140%;
margin-bottom:0.5ex; margin-bottom: 0.5ex;
} }
h3 { h3 {
font-size:120%; font-size: 120%;
margin-bottom:0.25ex; margin-bottom: 0.25ex;
} }
h4 { h4 {
font-size:100%; font-size: 100%;
margin-bottom:0.25ex; margin-bottom: 0.25ex;
} }
h1, h2, h3, h4 { h1, h2, h3, h4 {
page-break-after:avoid; page-break-after: avoid;
} }
p { p {
margin-top:0.75ex; margin-top: 0.75ex;
margin-bottom:0.75ex; margin-bottom: 0.75ex;
} }
body h1 + p { body h1 + p {
margin-top:1.5ex; margin-top: 1.5ex;
margin-bottom:0.75ex; margin-bottom: 0.75ex;
} }
footer { footer {
font-size:75%; font-size: 75%;
margin-top: 1em; margin-top: 1em;
border-top: 1px solid darkgray; border-top: 1px solid darkgray;
padding-top: 1em; padding-top: 1em;
@ -97,44 +111,40 @@ footer a:only-of-type {
} }
small { small {
font-size:75%; font-size: 75%;
}
code {
font-size: 125%;
color: #008000;
} }
.indent { .indent {
text-indent:-4ex; text-indent: -4ex;
margin-left:4ex; margin-left: 4ex;
margin-top:0.5ex; margin-top: 0.5ex;
text-align:left; text-align: left;
} }
.box { .box {
margin-left:1ex; margin-left: 1ex;
margin-right:1ex; margin-right: 1ex;
margin-top:0.5ex; margin-top: 0.5ex;
padding: 0 1ex; padding: 0 1ex;
border: 1px solid black; border: thin solid black;
text-align:left; text-align: left;
background-color:#f0f0f0; background-color: #f0f0f0;
page-break-inside: avoid;
} }
#navleft { #navleft {
position:fixed; position: fixed;
left:0; left: 0;
top:0; top: 0;
padding-top:70px; padding-top: 70px;
width:14em; width: 14em;
height:100%; height: 100%;
border-style:solid; border-style: solid;
border-color:black; border-color: black;
border-width:0 1px 0 0; border-width: 0 1px 0 0;
background-color:#e3eaf6; background-color: #e3eaf6;
overflow:hidden; overflow: hidden;
z-index:0; z-index: 0;
} }
.new { .new {
@ -142,17 +152,18 @@ code {
} }
a[target=ex]:after { a[target=ex]:after {
content:" " url(ex.png); content: " " url(ex.png);
} }
a[target=ex]:hover:after { a[target=ex]:hover:after {
content: " " url(exr.png); content: " " url(exr.png);
} }
@media print { @media print {
a:link {text-decoration:none;} a:link { text-decoration: none; }
a[target=ex]:after {content:" [" attr(href) "]";} a[target=ex]:after { content:" [" attr(href) "]"; font-size: 75%; }
body {margin:0 4em;} body { margin: 0 4em; }
h1 {position:relative; background-position:0 0;} h1 { position: relative; background-position: 0 0; }
#navleft {display:none;} #navleft { display: none; }
footer { display: none; }
} }

View File

@ -293,9 +293,10 @@ record (stringout, "$(DEVICE):clean_2") {<br>
<a name="web"></a> <a name="web"></a>
<h2>I need to read a web page</h2> <h2>I need to read a web page</h2>
<p> <p>
First you have to send a correctly formatted HTML request. First you have to send a correctly formatted HTML header for a GET request.
Note that this request must contain the full URL like Note that this header must contain the full URL like
"http://server/page" and must be terminated with <u>two</u> newlines. "http://server/page" and must be terminated with <u>two</u>
CR LF sequences (<code>"\r\n\r\n"</code> or <code>CR LF CR LF</code>).
The server should be the same as in the The server should be the same as in the
<a href="setup.html#sta"><code>drvAsynIPPortConfigure</code></a> <a href="setup.html#sta"><code>drvAsynIPPortConfigure</code></a>
command (if not using a http proxy). command (if not using a http proxy).
@ -313,17 +314,18 @@ Read the title of a web page.
get_title {<br> get_title {<br>
&nbsp;&nbsp;extrainput = ignore;<br> &nbsp;&nbsp;extrainput = ignore;<br>
&nbsp;&nbsp;replyTimeout = 1000;<br> &nbsp;&nbsp;replyTimeout = 1000;<br>
&nbsp;&nbsp;out "GET http://\$1\n\n";<br> &nbsp;&nbsp;out "GET http://\$1\r\n\r\n";<br>
&nbsp;&nbsp;in "%+.1/(?im)&lt;title&gt(.*)&lt\/title&gt;/";<br> &nbsp;&nbsp;in "%+.1/(?im)&lt;title&gt(.*)&lt\/title&gt;/";<br>
} }
</code> </code>
</p> </p>
<p> <p>
Terminate the request with two newlines, either explicit like here Terminate the request with two carriage return + newlines, either explicit
<u>or</u> using an like here <u>or</u> using an
<a href="protocol.html#sysvar"><code>outTerminator</code></a>. <a href="protocol.html#sysvar"><code>outTerminator</code></a>.
The URI (without http:// but including the web server host name) The URI (without http:// but including the web server host name)
is passed as <a href="protocol.html#argvar">argument</a> 1 to <code>\$1</code>. is passed as <a href="protocol.html#argvar">argument</a> 1 to <code>\$1</code>
in this example.
Note that web servers may be slow, so allow some Note that web servers may be slow, so allow some
<a href="protocol.html#argvar"><code>replyTimeout</code></a>. <a href="protocol.html#argvar"><code>replyTimeout</code></a>.
</p> </p>
@ -390,7 +392,7 @@ Then we read the number.
get_title {<br> get_title {<br>
&nbsp;&nbsp;extrainput = ignore;<br> &nbsp;&nbsp;extrainput = ignore;<br>
&nbsp;&nbsp;replyTimeout = 1000;<br> &nbsp;&nbsp;replyTimeout = 1000;<br>
&nbsp;&nbsp;out "GET http://\$1\n\n";<br> &nbsp;&nbsp;out "GET http://\$1\r\n\r\n";<br>
&nbsp;&nbsp;in "%*/Interesting value:/%f more text";<br> &nbsp;&nbsp;in "%*/Interesting value:/%f more text";<br>
} }
</code> </code>

View File

@ -34,18 +34,16 @@ extern "C" {
#include "iocsh.h" #include "iocsh.h"
#endif #endif
#include "StreamBusInterface.h"
#include "StreamError.h"
#include "StreamBuffer.h"
#include "asynDriver.h" #include "asynDriver.h"
#include "asynOctet.h" #include "asynOctet.h"
#include "asynInt32.h" #include "asynInt32.h"
#include "asynUInt32Digital.h" #include "asynUInt32Digital.h"
#include "asynGpibDriver.h" #include "asynGpibDriver.h"
#include "StreamBusInterface.h"
#include "StreamError.h"
#include "StreamBuffer.h"
#include "devStream.h" #include "devStream.h"
#include "MacroMagic.h" #include "MacroMagic.h"
#define Z PRINTF_SIZE_T_PREFIX #define Z PRINTF_SIZE_T_PREFIX
@ -887,14 +885,11 @@ readHandler()
if (pasynOctet->setInputEos(pvtOctet, pasynUser, if (pasynOctet->setInputEos(pvtOctet, pasynUser,
deveos, (int)deveoslen) == asynSuccess) deveos, (int)deveoslen) == asynSuccess)
{ {
if (ioAction != AsyncRead) debug2("AsynDriverInterface::readHandler(%s) "
{ "input EOS changed from \"%s\" to \"%s\"\n",
debug("AsynDriverInterface::readHandler(%s) " clientName(),
"input EOS changed from \"%s\" to \"%s\"\n", StreamBuffer(oldeos, oldeoslen).expand()(),
clientName(), StreamBuffer(deveos, deveoslen).expand()());
StreamBuffer(oldeos, oldeoslen).expand()(),
StreamBuffer(deveos, deveoslen).expand()());
}
break; break;
} }
deveos++; deveoslen--; deveos++; deveoslen--;
@ -948,20 +943,18 @@ readHandler()
eomReason = 0; eomReason = 0;
pasynUser->errorMessage[0] = 0; pasynUser->errorMessage[0] = 0;
debug("AsynDriverInterface::readHandler(%s): ioAction=%s "
"read(..., bytesToRead=%" Z "u, ...) "
"[timeout=%g sec]\n",
clientName(), toStr(ioAction),
bytesToRead, pasynUser->timeout);
status = pasynOctet->read(pvtOctet, pasynUser, status = pasynOctet->read(pvtOctet, pasynUser,
buffer, bytesToRead, &received, &eomReason); buffer, bytesToRead, &received, &eomReason);
// Even though received is size_t I have seen (size_t)-1 here! // Even though received is size_t I have seen (size_t)-1 here
debug("AsynDriverInterface::readHandler(%s): " // in case half a terminator had been read last time!
"read returned %s: ioAction=%s received=%" Z "d, " if (!(status == asynTimeout && pasynUser->timeout == 0 && received == 0))
"eomReason=%s, buffer=\"%s\"\n", debug("AsynDriverInterface::readHandler(%s): ioAction=%s "
clientName(), toStr(status), toStr(ioAction), "read(%" Z "u bytes, timeout=%g sec) returned status %s: received=%" Z "d bytes, "
received, eomReasonToStr(eomReason), "eomReason=%s, buffer=\"%s\"\n",
StreamBuffer(buffer, received).expand()()); clientName(), toStr(ioAction),
bytesToRead, pasynUser->timeout, toStr(status), received,
eomReasonToStr(eomReason), StreamBuffer(buffer, received).expand()());
// asyn 4.16 sets reason to ASYN_EOM_END when device disconnects. // asyn 4.16 sets reason to ASYN_EOM_END when device disconnects.
// What about earlier versions? // What about earlier versions?
if (!connected) eomReason |= ASYN_EOM_END; if (!connected) eomReason |= ASYN_EOM_END;
@ -1037,7 +1030,7 @@ readHandler()
// reply timeout // reply timeout
if (ioAction == AsyncRead) if (ioAction == AsyncRead)
{ {
debug("AsynDriverInterface::readHandler(%s): " debug2("AsynDriverInterface::readHandler(%s): "
"no async input, retry in in %g seconds\n", "no async input, retry in in %g seconds\n",
clientName(), replyTimeout); clientName(), replyTimeout);
// start next poll after timer expires // start next poll after timer expires
@ -1140,9 +1133,10 @@ readHandler()
{ {
pasynOctet->setInputEos(pvtOctet, pasynUser, pasynOctet->setInputEos(pvtOctet, pasynUser,
oldeos, oldeoslen); oldeos, oldeoslen);
debug("AsynDriverInterface::readHandler(%s) " debug2("AsynDriverInterface::readHandler(%s) "
"input EOS restored to \"%s\"\n", "input EOS restored from \"%s\" to \"%s\"\n",
clientName(), clientName(),
StreamBuffer(deveos, deveoslen).expand()(),
StreamBuffer(oldeos, oldeoslen).expand()()); StreamBuffer(oldeos, oldeoslen).expand()());
} }
} }
@ -1360,8 +1354,6 @@ timerExpired()
// at the moment if another asynUser got input right now. // at the moment if another asynUser got input right now.
// queueRequest might fail if another request was just queued // queueRequest might fail if another request was just queued
pasynManager->isAutoConnect(pasynUser, &autoconnect); pasynManager->isAutoConnect(pasynUser, &autoconnect);
debug("%s: polling for I/O Intr: autoconnected: %d, connect: %d\n",
clientName(), autoconnect, connected);
if (autoconnect && !connected) if (autoconnect && !connected)
{ {
// has explicitely been disconnected // has explicitely been disconnected
@ -1375,7 +1367,7 @@ timerExpired()
asynStatus status = pasynManager->queueRequest(pasynUser, asynStatus status = pasynManager->queueRequest(pasynUser,
asynQueuePriorityLow, -1.0); asynQueuePriorityLow, -1.0);
// if this fails, we are already queued by another thread // if this fails, we are already queued by another thread
debug("AsynDriverInterface::timerExpired %s: " debug2("AsynDriverInterface::timerExpired %s: "
"queueRequest(..., priority=Low, queueTimeout=-1) = %s %s\n", "queueRequest(..., priority=Low, queueTimeout=-1) = %s %s\n",
clientName(), toStr(status), clientName(), toStr(status),
status!=asynSuccess ? pasynUser->errorMessage : ""); status!=asynSuccess ? pasynUser->errorMessage : "");
@ -1493,7 +1485,7 @@ void AsynDriverInterface::
handleRequest() handleRequest()
{ {
cancelTimer(); cancelTimer();
debug("AsynDriverInterface::handleRequest(%s) %s\n", debug2("AsynDriverInterface::handleRequest(%s) %s\n",
clientName(), toStr(ioAction)); clientName(), toStr(ioAction));
switch (ioAction) switch (ioAction)
{ {

View File

@ -22,6 +22,7 @@
#include <ctype.h> #include <ctype.h>
#include <limits.h> #include <limits.h>
#include "StreamFormatConverter.h" #include "StreamFormatConverter.h"
#include "StreamError.h" #include "StreamError.h"

236
src/ChecksumConverter.cc Executable file → Normal file
View File

@ -20,45 +20,64 @@
* along with StreamDevice. If not, see https://www.gnu.org/licenses/. * along with StreamDevice. If not, see https://www.gnu.org/licenses/.
*************************************************************************/ *************************************************************************/
#include "StreamFormatConverter.h" #ifdef vxWorks
#include "StreamError.h" #include <version.h>
#include <ctype.h> /* VxWorks has strncasecmp since version 6
#if defined(__vxworks) || defined(vxWorks) but availability depends on configuration.
#define PRIX32 "lX" We cannot know.
#define PRIu32 "lu" */
#define PRIX8 "X" #define NEED_strncasecmp
#define SCNx8 "hhx" /* VxWorks does not have inttypes.h and uint32_t differs between versions */
#define uint_fast8_t uint8_t #if defined(_WRS_VXWORKS_MAJOR) && (_WRS_VXWORKS_MAJOR > 6 || (_WRS_VXWORKS_MAJOR == 6 && _WRS_VXWORKS_MINOR >= 9))
#define int_fast8_t int8_t #define PRIX32 "X"
#elif defined(_MSC_VER) && _MSC_VER < 1700 /* Visual Studio 2010 does not have inttypes.h */ #define PRIu32 "u"
#include <stdint.h> #else
#define PRIX32 "X" #define PRIX32 "lX"
#define PRIu32 "u" #define PRIu32 "lu"
#define PRIX8 "X" #endif
#define SCNx8 "hhx" #define PRIX8 "X"
#elif defined(_MSC_VER) && _MSC_VER < 1700
/* Visual Studio 2010 does not have inttypes.h */
#define PRIX32 "X"
#define PRIu32 "u"
#define PRIX8 "X"
#else #else
#define __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS
#include <stdint.h> #include <inttypes.h>
#include <inttypes.h>
#endif #endif
#if defined(__vxworks) || defined(vxWorks) || defined(_WIN32) || defined(__rtems__) #include <ctype.h>
// These systems have no strncasecmp #include <stdlib.h>
#include "epicsVersion.h"
#ifdef BASE_VERSION #if defined(__rtems__)
// 3.13 #include <rtems.h>
static int strncasecmp(const char *s1, const char *s2, size_t n) #if __RTEMS_MAJOR__ < 5
{ /* RTEMS has strncasecmp since version 5 */
int r=0; #define NEED_strncasecmp
while (n && (r = toupper(*s1)-toupper(*s2)) == 0) { n--; s1++; s2++; }; #endif
return r; #endif
#ifdef _MSC_VER
/* Windows strncasecmp has a different name. */
#define strncasecmp _strnicmp
#endif
#ifdef NEED_strncasecmp
// Have no strncasecmp but avoid compiler errors in case it exists in future versions
extern "C" {
static int mystrncasecmp(const char *s1, const char *s2, size_t n)
{
int r=0;
while (n && (r = toupper(*s1)-toupper(*s2)) == 0) { n--; s1++; s2++; };
return r;
}
} }
#else #define strncasecmp mystrncasecmp
#include "epicsString.h"
#define strncasecmp epicsStrnCaseCmp
#endif
#endif #endif
#include "StreamFormatConverter.h"
#include "StreamError.h"
typedef uint32_t (*checksumFunc)(const uint8_t* data, size_t len, uint32_t init); typedef uint32_t (*checksumFunc)(const uint8_t* data, size_t len, uint32_t init);
static uint32_t sum(const uint8_t* data, size_t len, uint32_t sum) static uint32_t sum(const uint8_t* data, size_t len, uint32_t sum)
@ -84,6 +103,33 @@ static uint32_t xor7(const uint8_t* data, size_t len, uint32_t sum)
return xor8(data, len, sum) & 0x7F; return xor8(data, len, sum) & 0x7F;
} }
static uint32_t bitsum(const uint8_t* data, size_t len, uint32_t sum)
{
// number of set bits in each byte
const uint8_t table[256] = {
0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,
1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8};
while (len--)
{
sum += table[*data++];
}
return sum;
}
static uint32_t crc_0x07(const uint8_t* data, size_t len, uint32_t crc) static uint32_t crc_0x07(const uint8_t* data, size_t len, uint32_t crc)
{ {
// x^8 + x^2 + x^1 + x^0 (0x07) // x^8 + x^2 + x^1 + x^0 (0x07)
@ -502,6 +548,7 @@ static uint32_t leybold(const uint8_t* data, size_t len, uint32_t sum)
sum += *data++; sum += *data++;
} }
sum = ~sum; sum = ~sum;
sum &= 0xff;
if (sum < 32) sum+=32; if (sum < 32) sum+=32;
return sum; return sum;
} }
@ -517,6 +564,67 @@ static uint32_t brksCryo(const uint8_t* data, size_t len, uint32_t sum)
return xsum; return xsum;
} }
// Longitudinal Redundancy Check
static uint32_t lrc(const uint8_t* data, size_t len, uint32_t sum)
{
while (len--) {
sum = (sum + (*data++)) & 0xFF;
}
sum = ((sum ^ 0xFF) + 1) & 0xFF;
return sum;
}
// Longitudinal Redundancy Check using ASCII representation of numbers, 2-by-2
static uint32_t hexlrc(const uint8_t* data, size_t len, uint32_t sum)
{
uint32_t d;
uint32_t final_digit = 0;
while (len--)
{
d = toupper(*data++);
// Convert all hex digits, ignore all other bytes
if (isxdigit(d))
{
// Convert digits from ASCII to number
if (isdigit(d)) {
d -= '0';
}
else {
d -= 'A' - 0x0A;
}
// For the most significant bits, shift 4 bits
if (len % 2) {
final_digit = d << 4;
// Least significant bits are summed to previous converted digit
} else {
d += final_digit;
final_digit = 0;
// Apply lrc rule
sum = (sum + d) & 0xFF;
}
}
}
// Apply lrc rule
sum = ((sum ^ 0xFF) + 1) & 0xFF;
return sum;
}
// Checksum used by Spellman High Voltage Supplies MPS
static uint32_t hv_mps(const uint8_t* data, size_t len, uint32_t sum)
{
while (len--)
{
sum += *data++;
}
return (~sum & 0x7F) | 0x40;
}
struct checksum struct checksum
{ {
@ -560,20 +668,27 @@ static checksum checksumMap[] =
{"hexsum8", hexsum, 0x00, 0x00, 1}, // 0x2D {"hexsum8", hexsum, 0x00, 0x00, 1}, // 0x2D
{"cpi", CPI, 0x00, 0x00, 1}, // 0x7E {"cpi", CPI, 0x00, 0x00, 1}, // 0x7E
{"leybold", leybold, 0x00, 0x00, 1}, // 0x22 {"leybold", leybold, 0x00, 0x00, 1}, // 0x22
{"brksCryo",brksCryo, 0x00, 0x00, 1} // 0x4A {"brksCryo",brksCryo, 0x00, 0x00, 1}, // 0x4A
{"lrc", lrc, 0x00, 0x00, 1}, // 0x23
{"hexlrc", hexlrc, 0x00, 0x00, 1}, // 0xA7
{"bitsum", bitsum, 0x00, 0x00, 1}, // 0x21
{"bitsum8", bitsum, 0x00, 0x00, 1}, // 0x21
{"bitsum16",bitsum, 0x0000, 0x0000, 2}, // 0x0021
{"bitsum32",bitsum, 0x00000000, 0x00000000, 4}, // 0x00000021
{"hv_mps", hv_mps, 0xFF, 0x00, 1} // 0x63
}; };
static uint32_t mask[5] = {0, 0xFF, 0xFFFF, 0xFFFFFF, 0xFFFFFFFF}; static uint32_t mask[5] = {0, 0xFF, 0xFFFF, 0xFFFFFF, 0xFFFFFFFF};
class ChecksumConverter : public StreamFormatConverter class ChecksumConverter : public StreamFormatConverter
{ {
int parse (const StreamFormat&, StreamBuffer&, const char*&, bool); int parse (const StreamFormat&, StreamBuffer&, const char*&, bool scanFormat);
bool printPseudo(const StreamFormat&, StreamBuffer&); bool printPseudo(const StreamFormat&, StreamBuffer&);
ssize_t scanPseudo(const StreamFormat&, StreamBuffer&, size_t& cursor); ssize_t scanPseudo(const StreamFormat&, StreamBuffer&, size_t& cursor);
}; };
int ChecksumConverter:: int ChecksumConverter::
parse(const StreamFormat&, StreamBuffer& info, const char*& source, bool) parse(const StreamFormat&, StreamBuffer& info, const char*& source, bool scanFormat)
{ {
const char* p = strchr(source, '>'); const char* p = strchr(source, '>');
if (!p) if (!p)
@ -627,7 +742,7 @@ parse(const StreamFormat&, StreamBuffer& info, const char*& source, bool)
info.append(&xorout, sizeof(xorout)); info.append(&xorout, sizeof(xorout));
info.append(fnum); info.append(fnum);
source = p+1; source = p+1;
return pseudo_format; return scanFormat ? needs_original_format : pseudo_format;
} }
} }
@ -642,11 +757,16 @@ printPseudo(const StreamFormat& format, StreamBuffer& output)
const char* info = format.info; const char* info = format.info;
uint32_t init = extract<uint32_t>(info); uint32_t init = extract<uint32_t>(info);
uint32_t xorout = extract<uint32_t>(info); uint32_t xorout = extract<uint32_t>(info);
uint_fast8_t fnum = extract<uint8_t>(info); uint8_t fnum = extract<uint8_t>(info);
size_t start = format.width; size_t start = format.width;
size_t length = output.length()-format.width; size_t length = output.length();
if (format.prec > 0) length -= format.prec; if (length >= start) length -= start;
else length = 0;
if (format.prec > 0) {
if (length >= (size_t)format.prec) length -= format.prec;
else length = 0;
}
debug("ChecksumConverter %s: output to check: \"%s\"\n", debug("ChecksumConverter %s: output to check: \"%s\"\n",
checksumMap[fnum].name, output.expand(start,length)()); checksumMap[fnum].name, output.expand(start,length)());
@ -658,8 +778,8 @@ printPseudo(const StreamFormat& format, StreamBuffer& output)
debug("ChecksumConverter %s: output checksum is 0x%" PRIX32 "\n", debug("ChecksumConverter %s: output checksum is 0x%" PRIX32 "\n",
checksumMap[fnum].name, sum); checksumMap[fnum].name, sum);
uint_fast8_t i; uint8_t i;
uint_fast8_t outchar; uint8_t outchar;
if (format.flags & sign_flag) // decimal if (format.flags & sign_flag) // decimal
{ {
@ -718,15 +838,19 @@ scanPseudo(const StreamFormat& format, StreamBuffer& input, size_t& cursor)
uint32_t init = extract<uint32_t>(info); uint32_t init = extract<uint32_t>(info);
uint32_t xorout = extract<uint32_t>(info); uint32_t xorout = extract<uint32_t>(info);
size_t start = format.width; size_t start = format.width;
uint_fast8_t fnum = extract<uint8_t>(info); uint8_t fnum = extract<uint8_t>(info);
size_t length = cursor-format.width; size_t length = cursor;
if (length >= start) length -= start;
else length = 0;
if (format.prec > 0) {
if (length >= (size_t)format.prec) length -= format.prec;
else length = 0;
}
if (format.prec > 0) length -= format.prec; debug("ChecksumConverter %s: input to check: \"%s\"\n",
debug("ChecksumConverter %s: input to check: \"%s\n",
checksumMap[fnum].name, input.expand(start,length)()); checksumMap[fnum].name, input.expand(start,length)());
uint_fast8_t nDigits = uint8_t nDigits =
// get number of decimal digits from number of bytes: ceil(bytes*2.5) // get number of decimal digits from number of bytes: ceil(bytes*2.5)
format.flags & sign_flag ? (checksumMap[fnum].bytes + 1) * 25 / 10 - 2 : format.flags & sign_flag ? (checksumMap[fnum].bytes + 1) * 25 / 10 - 2 :
format.flags & (zero_flag|left_flag) ? 2 * checksumMap[fnum].bytes : format.flags & (zero_flag|left_flag) ? 2 * checksumMap[fnum].bytes :
@ -735,8 +859,8 @@ scanPseudo(const StreamFormat& format, StreamBuffer& input, size_t& cursor)
if ((ssize_t)( input.length() - cursor ) < expectedLength) if ((ssize_t)( input.length() - cursor ) < expectedLength)
{ {
debug("ChecksumConverter %s: Input '%s' too short for checksum\n", debug("ChecksumConverter %s: Input '%s' too short (%zu-%zu<%zu) for checksum\n",
checksumMap[fnum].name, input.expand(cursor)()); checksumMap[fnum].name, input.expand(cursor)(), input.length(), cursor, expectedLength);
return -1; return -1;
} }
@ -747,7 +871,7 @@ scanPseudo(const StreamFormat& format, StreamBuffer& input, size_t& cursor)
debug("ChecksumConverter %s: input checksum is 0x%0*" PRIX32 "\n", debug("ChecksumConverter %s: input checksum is 0x%0*" PRIX32 "\n",
checksumMap[fnum].name, 2*checksumMap[fnum].bytes, sum); checksumMap[fnum].name, 2*checksumMap[fnum].bytes, sum);
uint_fast8_t inchar; unsigned int inchar;
if (format.flags & sign_flag) // decimal if (format.flags & sign_flag) // decimal
{ {
@ -769,12 +893,12 @@ scanPseudo(const StreamFormat& format, StreamBuffer& input, size_t& cursor)
else else
if (format.flags & alt_flag) // lsb first (little endian) if (format.flags & alt_flag) // lsb first (little endian)
{ {
uint_fast8_t i; uint8_t i;
for (i = 0; i < checksumMap[fnum].bytes; i++) for (i = 0; i < checksumMap[fnum].bytes; i++)
{ {
if (format.flags & zero_flag) // ASCII if (format.flags & zero_flag) // ASCII
{ {
if (sscanf(input(cursor+2*i), "%2" SCNx8, (int8_t *) &inchar) != 1) if (sscanf(input(cursor+2*i), "%2x", &inchar) != 1)
{ {
debug("ChecksumConverter %s: Input byte '%s' is not a hex byte\n", debug("ChecksumConverter %s: Input byte '%s' is not a hex byte\n",
checksumMap[fnum].name, input.expand(cursor+2*i,2)()); checksumMap[fnum].name, input.expand(cursor+2*i,2)());
@ -812,13 +936,13 @@ scanPseudo(const StreamFormat& format, StreamBuffer& input, size_t& cursor)
} }
else // msb first (big endian) else // msb first (big endian)
{ {
int_fast8_t i; int8_t i;
uint_fast8_t j; uint8_t j;
for (i = checksumMap[fnum].bytes-1, j = 0; i >= 0; i--, j++) for (i = checksumMap[fnum].bytes-1, j = 0; i >= 0; i--, j++)
{ {
if (format.flags & zero_flag) // ASCII if (format.flags & zero_flag) // ASCII
{ {
sscanf(input(cursor+2*i), "%2" SCNx8, (int8_t *) &inchar); sscanf(input(cursor+2*i), "%2x", &inchar);
} }
else else
if (format.flags & left_flag) // poor man's hex: 0x30 - 0x3F if (format.flags & left_flag) // poor man's hex: 0x30 - 0x3F

View File

@ -20,10 +20,11 @@
* along with StreamDevice. If not, see https://www.gnu.org/licenses/. * along with StreamDevice. If not, see https://www.gnu.org/licenses/.
*************************************************************************/ *************************************************************************/
#include <stdlib.h>
#include "StreamFormatConverter.h" #include "StreamFormatConverter.h"
#include "StreamError.h" #include "StreamError.h"
#include "StreamProtocol.h" #include "StreamProtocol.h"
#include <stdlib.h>
// Enum %{string0|string1|...} // Enum %{string0|string1|...}
@ -76,7 +77,7 @@ parse(const StreamFormat& fmt, StreamBuffer& info,
numEnums = -(numEnums+1); numEnums = -(numEnums+1);
info.append('\0'); info.append('\0');
memcpy(info(n), &numEnums, sizeof(numEnums)); memcpy(info(n), &numEnums, sizeof(numEnums));
debug("EnumConverter::parse %ld choices with default: %s\n", debug2("EnumConverter::parse %ld choices with default: %s\n",
-numEnums, info.expand()()); -numEnums, info.expand()());
return enum_format; return enum_format;
} }
@ -99,7 +100,7 @@ parse(const StreamFormat& fmt, StreamBuffer& info,
if (*source++ == '}') if (*source++ == '}')
{ {
memcpy(info(n), &numEnums, sizeof(numEnums)); memcpy(info(n), &numEnums, sizeof(numEnums));
debug("EnumConverter::parse %ld choices: %s\n", debug2("EnumConverter::parse %ld choices: %s\n",
numEnums, info.expand()()); numEnums, info.expand()());
return enum_format; return enum_format;
} }

View File

@ -27,12 +27,12 @@ include $(TOP)/configure/CONFIG
-include CONFIG_STREAM -include CONFIG_STREAM
-include ../CONFIG_STREAM -include ../CONFIG_STREAM
LIBRARY_DEFAULT = stream LIBRARY_IOC = stream
DBD += $(LIBRARY_DEFAULT).dbd DBD += stream.dbd
DBD += $(LIBRARY_DEFAULT)-base.dbd DBD += stream-base.dbd
ifdef CALC ifneq ($(words $(CALC) $(SYNAPPS)), 0)
DBD += $(LIBRARY_DEFAULT)-scalcout.dbd DBD += stream-scalcout.dbd
endif endif
ifdef ASYN ifdef ASYN
@ -42,7 +42,7 @@ $(warning Asyn not included! Didn't you set ASYN in your RELEASE file?)
endif endif
ifeq ($(LOADABLE_MODULE),YES) ifeq ($(LOADABLE_MODULE),YES)
SRCS += $(LIBRARY_DEFAULT)_registerRecordDeviceDriver.cpp SRCS += stream_registerRecordDeviceDriver.cpp
endif endif
SRCS += $(BUSSES:%=%Interface.cc) SRCS += $(BUSSES:%=%Interface.cc)
SRCS += $(FORMATS:%=%Converter.cc) SRCS += $(FORMATS:%=%Converter.cc)
@ -60,6 +60,9 @@ ifneq ($(words $(PCRE_LIB) $(PCRE_INCLUDE)),0)
LIB_SYS_LIBS_DEFAULT += pcre LIB_SYS_LIBS_DEFAULT += pcre
LIB_SYS_LIBS_WIN32 += $(PCRE_LIB)\\pcre LIB_SYS_LIBS_WIN32 += $(PCRE_LIB)\\pcre
SHRLIB_DEPLIB_DIRS += $(PCRE_LIB) SHRLIB_DEPLIB_DIRS += $(PCRE_LIB)
ifdef ENABLE_STATIC
CPPFLAGS += -DPCRE_STATIC
endif
endif endif
endif endif
@ -71,6 +74,10 @@ INC += StreamFormatConverter.h
INC += StreamBuffer.h INC += StreamBuffer.h
INC += StreamError.h INC += StreamError.h
INC += StreamVersion.h INC += StreamVersion.h
INC += StreamProtocol.h
INC += StreamBusInterface.h
INC += StreamCore.h
INC += MacroMagic.h
# switch off annoying rset warnings in 3.16+ # switch off annoying rset warnings in 3.16+
CPPFLAGS += -DUSE_TYPED_RSET CPPFLAGS += -DUSE_TYPED_RSET
@ -94,28 +101,28 @@ streamReferences: ../CONFIG_STREAM
$(PERL) ../makeref.pl Converter $(FORMATS) >> $@ $(PERL) ../makeref.pl Converter $(FORMATS) >> $@
# create stream-base.dbd from all RECORDTYPES except scalcout record # create stream-base.dbd from all RECORDTYPES except scalcout record
$(COMMON_DIR)/$(LIBRARY_DEFAULT)-base.dbd: ../CONFIG_STREAM $(COMMON_DIR)/stream-base.dbd: ../CONFIG_STREAM
$(PERL) ../makedbd.pl $(if $(ASYN),--with-asyn) $(if $(BASE_3_14),,-3.13) $(filter-out scalcout, $(RECORDTYPES)) > $@ $(PERL) ../makedbd.pl $(if $(ASYN),--with-asyn) $(if $(BASE_3_14),,-3.13) $(filter-out scalcout, $(RECORDTYPES)) > $@
$(LIBRARY_DEFAULT)-base.dbd$(DEP): ../CONFIG_STREAM stream-base.dbd$(DEP): ../CONFIG_STREAM
echo $(LIBRARY_DEFAULT)-base.dbd: $< > $@ echo stream-base.dbd: $< > $@
STREAM_DBD_FILES = $(LIBRARY_DEFAULT)-base.dbd STREAM_DBD_FILES = stream-base.dbd
ifdef CALC ifneq ($(words $(CALC) $(SYNAPPS)), 0)
# create stream-scalcout.dbd for scalcout record # create stream-scalcout.dbd for scalcout record
$(COMMON_DIR)/$(LIBRARY_DEFAULT)-scalcout.dbd: ../CONFIG_STREAM $(COMMON_DIR)/stream-scalcout.dbd: ../CONFIG_STREAM
$(PERL) ../makedbd.pl --rec-only scalcout > $@ $(PERL) ../makedbd.pl --rec-only scalcout > $@
$(LIBRARY_DEFAULT)-scalcout.dbd$(DEP): ../CONFIG_STREAM stream-scalcout.dbd$(DEP): ../CONFIG_STREAM
echo $(LIBRARY_DEFAULT)-scalcout.dbd: $< > $@ echo stream-scalcout.dbd: $< > $@
STREAM_DBD_FILES += $(LIBRARY_DEFAULT)-scalcout.dbd STREAM_DBD_FILES += stream-scalcout.dbd
endif endif
# create stream.dbd for all record types # create stream.dbd for all record types
$(COMMON_DIR)/$(LIBRARY_DEFAULT).dbd: ../CONFIG_STREAM $(COMMON_DIR)/stream.dbd: ../CONFIG_STREAM
$(PERL) ../makedbd.pl $(if $(ASYN),--with-asyn) $(if $(BASE_3_14),,-3.13) $(RECORDTYPES) > $@ $(PERL) ../makedbd.pl $(if $(ASYN),--with-asyn) $(if $(BASE_3_14),,-3.13) $(RECORDTYPES) > $@
$(LIBRARY_DEFAULT).dbd$(DEP): ../CONFIG_STREAM stream.dbd$(DEP): ../CONFIG_STREAM
echo $(LIBRARY_DEFAULT).dbd: $< > $@ echo stream.dbd: $< > $@

View File

@ -22,9 +22,10 @@
* along with StreamDevice. If not, see https://www.gnu.org/licenses/. * along with StreamDevice. If not, see https://www.gnu.org/licenses/.
*************************************************************************/ *************************************************************************/
#include <math.h>
#include "StreamFormatConverter.h" #include "StreamFormatConverter.h"
#include "StreamError.h" #include "StreamError.h"
#include <math.h>
// Exponential Converter %m // Exponential Converter %m
// Eric Berryman requested a double format that reads // Eric Berryman requested a double format that reads

View File

@ -20,13 +20,15 @@
* along with StreamDevice. If not, see https://www.gnu.org/licenses/. * along with StreamDevice. If not, see https://www.gnu.org/licenses/.
*************************************************************************/ *************************************************************************/
#include "StreamFormatConverter.h"
#include "StreamError.h"
#include "pcre.h"
#include <string.h> #include <string.h>
#include <limits.h> #include <limits.h>
#include <ctype.h> #include <ctype.h>
#include "pcre.h"
#include "StreamFormatConverter.h"
#include "StreamError.h"
#define Z PRINTF_SIZE_T_PREFIX #define Z PRINTF_SIZE_T_PREFIX
// Perl regular expressions (PCRE) %/regexp/ and %#/regexp/subst/ // Perl regular expressions (PCRE) %/regexp/ and %#/regexp/subst/

View File

@ -25,9 +25,6 @@
#define _BSD_SOURCE #define _BSD_SOURCE
#endif #endif
#include "StreamBuffer.h"
#include "StreamError.h"
#include <stdio.h> #include <stdio.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdlib.h> #include <stdlib.h>
@ -40,6 +37,7 @@
#include <vxWorks.h> #include <vxWorks.h>
#include <fioLib.h> #include <fioLib.h>
#include <string.h>
struct outStr_s { struct outStr_s {
char *str; char *str;
@ -74,6 +72,9 @@ int vsnprintf(char *str, size_t size, const char *format, va_list ap) {
#endif // ! _WRS_VXWORKS_MAJOR #endif // ! _WRS_VXWORKS_MAJOR
#endif // vxWorks #endif // vxWorks
#include "StreamBuffer.h"
#include "StreamError.h"
#define P PRINTF_SIZE_T_PREFIX #define P PRINTF_SIZE_T_PREFIX
void StreamBuffer:: void StreamBuffer::
@ -101,7 +102,7 @@ init(const void* s, ssize_t minsize)
} }
// How the buffer looks like: // How the buffer looks like:
// |----free-----|####used####|--------00--------| // |----junk-----|####used####|--------00--------|
///|<--- offs -->|<-- len --->|<- cap-offs-len ->| ///|<--- offs -->|<-- len --->|<- cap-offs-len ->|
// 0 offs offs+len cap // 0 offs offs+len cap
// |<-------------- minsize ---------------> // |<-------------- minsize --------------->
@ -253,39 +254,71 @@ replace(ssize_t remstart, ssize_t remlen, const void* ins, ssize_t inslen)
if (inslen < 0) inslen = 0; if (inslen < 0) inslen = 0;
size_t remend = remstart+remlen; size_t remend = remstart+remlen;
size_t newlen = len+inslen-remlen; size_t newlen = len+inslen-remlen;
// How the buffer looks like before and after:
// |---junk---|##content_start##|/////////remove_this////////|##content_end##|0000|
// |<- offs ->|<-- remstart --->|<--------- remlen --------->| | |
// | |<--------------------- len ---------------------------------->| |
// 0 offs offs+remstart offs+remend offs+len cap
//
// If content size stays the same, no need to move old content:
// |---junk---|##content_start##|+++++++inserted_text++++++++|##content_end##|0000|
// |<----- inslen==remlen ----->| newlen==len
//
// If content shrinks (need to clear end of buffer): |< clear this >|
// |---junk---|##content_start##|inserted_text|##content_end##|00000000000000|0000|
// 0 offs |<- inslen -->| |< len-newlen >|
// offs+newlen offs+len
//
// If content grows but still fits (make sure to keep at least one 0 byte at end):
// |---junk---|##content_start##|++++++++inserted_text++++++++++|##content_end##|0|
// |<- offs ->|<--------------------- newlen ---------------------------------->| |
// 0 offs offs+newlen<cap
//
// If content would overflow, moving to offs 0 may help:
// May need to clear end if newlen < offs+len: |<clear>|
// |##content_start##|++++++++inserted_text++++++++++|##content_end##|0000000|0000|
// |<--------------------- newlen ---------------------------------->| |
// newlen offs+len
//
// Otherwise we need to copy to a new buffer.
if (cap <= newlen) if (cap <= newlen)
{ {
// buffer too short // buffer too short, copy to new buffer
size_t newcap; size_t newcap;
for (newcap = sizeof(local)*2; newcap <= newlen; newcap *= 2); for (newcap = sizeof(local)*2; newcap <= newlen; newcap *= 2);
char* newbuffer = new char[newcap]; char* newbuffer = new char[newcap];
memcpy(newbuffer, buffer+offs, remstart); memcpy(newbuffer, buffer+offs, remstart); // copy content start
memcpy(newbuffer+remstart, ins, inslen); memcpy(newbuffer+remstart, ins, inslen); // insert
memcpy(newbuffer+remstart+inslen, buffer+offs+remend, len-remend); memcpy(newbuffer+remstart+inslen, buffer+offs+remend, len-remend); // copy content end
memset(newbuffer+newlen, 0, newcap-newlen); memset(newbuffer+newlen, 0, newcap-newlen); // clear buffer end
if (buffer != local) if (buffer != local)
{ delete[] buffer;
delete [] buffer;
}
buffer = newbuffer; buffer = newbuffer;
cap = newcap; cap = newcap;
offs = 0; offs = 0;
} }
else else
{ {
if (newlen+offs<=cap) if (offs+newlen < cap)
{ {
// move to start of buffer // modified content still fits with current offs, just move content end
memmove(buffer+offs+remstart+inslen, buffer+offs+remend, len-remend); if (newlen != len)
memcpy(buffer+offs+remstart, ins, inslen); memmove(buffer+offs+remstart+inslen, buffer+offs+remend, len-remend); // move old content end if necessary
if (newlen<len) memset(buffer+offs+newlen, 0, len-newlen); memcpy(buffer+offs+remstart, ins, inslen); // insert before
if (newlen < len)
memset(buffer+offs+newlen, 0, len-newlen); // clear buffer end if content shrunk
} }
else else
{ {
memmove(buffer,buffer+offs,remstart); // move content to start of buffer
memmove(buffer+remstart+inslen, buffer+offs+remend, len-remend); memmove(buffer, buffer+offs, remstart); // move content start to 0 offs
memcpy(buffer+remstart, ins, inslen); memmove(buffer+remstart+inslen, buffer+offs+remend, len-remend); // move content end
if (newlen<len) memset(buffer+newlen, 0, len-newlen); memcpy(buffer+remstart, ins, inslen); // insert in between
if (newlen < offs+len)
memset(buffer+newlen, 0, offs+len-newlen); // clear buffer end if necessary
offs = 0; offs = 0;
} }
} }
@ -341,7 +374,10 @@ StreamBuffer StreamBuffer::expand(ssize_t start, ssize_t length) const
{ {
c = buffer[i]; c = buffer[i];
if (c < 0x20 || c >= 0x7f) if (c < 0x20 || c >= 0x7f)
result.print("\033[7m<%02x>\033[27m", c & 0xff); result.print("%s<%02x>%s",
ansiEscape(ANSI_REVERSE_VIDEO),
c & 0xff,
ansiEscape(ANSI_NOT_REVERSE_VIDEO));
else else
result.append(c); result.append(c);
} }
@ -354,18 +390,21 @@ dump() const
StreamBuffer result; StreamBuffer result;
size_t i; size_t i;
result.print("%" P "d,%" P "d,%" P "d:", offs, len, cap); result.print("%" P "d,%" P "d,%" P "d:", offs, len, cap);
if (offs) result.print("\033[47m"); if (offs) result.print("%s", ansiEscape(ANSI_BG_WHITE));
char c; char c;
for (i = 0; i < cap; i++) for (i = 0; i < cap; i++)
{ {
c = buffer[i]; c = buffer[i];
if (offs && i == offs) result.append("\033[0m"); if (offs && i == offs) result.append(ansiEscape(ANSI_RESET));
if (c < 0x20 || c >= 0x7f) if (c < 0x20 || c >= 0x7f)
result.print("\033[7m<%02x>\033[27m", c & 0xff); result.print("%s<%02x>%s",
ansiEscape(ANSI_REVERSE_VIDEO),
c & 0xff,
ansiEscape(ANSI_NOT_REVERSE_VIDEO));
else else
result.append(c); result.append(c);
if (i == offs+len-1) result.append("\033[47m"); if (i == offs+len-1) result.append(ansiEscape(ANSI_BG_WHITE));
} }
result.append("\033[0m"); result.append(ansiEscape(ANSI_RESET));
return result; return result;
} }

View File

@ -21,6 +21,7 @@
*************************************************************************/ *************************************************************************/
#include <stdio.h> #include <stdio.h>
#include "StreamBusInterface.h" #include "StreamBusInterface.h"
#include "StreamError.h" #include "StreamError.h"

View File

@ -20,13 +20,16 @@
* along with StreamDevice. If not, see https://www.gnu.org/licenses/. * along with StreamDevice. If not, see https://www.gnu.org/licenses/.
*************************************************************************/ *************************************************************************/
#include "StreamCore.h"
#include "StreamError.h"
#include <ctype.h> #include <ctype.h>
#include <stdlib.h> #include <stdlib.h>
#include "StreamCore.h"
#include "StreamError.h"
#define Z PRINTF_SIZE_T_PREFIX #define Z PRINTF_SIZE_T_PREFIX
int streamErrorDeadTime = 0;
/// debug functions ///////////////////////////////////////////// /// debug functions /////////////////////////////////////////////
char* StreamCore:: char* StreamCore::
@ -72,9 +75,9 @@ printCommands(StreamBuffer& buffer, const char* c)
buffer.append(" disconnect;\n"); buffer.append(" disconnect;\n");
break; break;
default: default:
buffer.append("\033[31;1mGARBAGE: "); buffer.append(ansiEscape(ANSI_RED_BOLD)).append("GARBAGE: ");
c = StreamProtocolParser::printString(buffer, c-1); c = StreamProtocolParser::printString(buffer, c-1);
buffer.append("\033[0m\n"); buffer.append(ansiEscape(ANSI_RESET)).append("\n");
} }
} }
} }
@ -122,12 +125,11 @@ printProtocol(FILE* file)
StreamCore* StreamCore::first = NULL; StreamCore* StreamCore::first = NULL;
StreamCore:: StreamCore::
StreamCore() : activeCommand(end) StreamCore() : StreamBusInterface::Client(),
next(), streamname(), flags(None), inTerminatorDefined(), outTerminatorDefined(),
activeCommand(end), previousResult(Success), numberOfErrors(0), unparsedInput()
{ {
businterface = NULL; businterface = NULL;
flags = None;
next = NULL;
unparsedInput = false;
// add myself to list of streams // add myself to list of streams
StreamCore** pstream; StreamCore** pstream;
for (pstream = &first; *pstream; pstream = &(*pstream)->next); for (pstream = &first; *pstream; pstream = &(*pstream)->next);
@ -489,6 +491,8 @@ finishProtocol(ProtocolResult status)
switch (status) switch (status)
{ {
case Success: case Success:
previousResult = Success;
numberOfErrors = 0;
handler = NULL; handler = NULL;
break; break;
case WriteTimeout: case WriteTimeout:
@ -599,6 +603,7 @@ evalOut()
// flush all unread input // flush all unread input
unparsedInput = false; unparsedInput = false;
inputBuffer.clear(); inputBuffer.clear();
inputLine.clear();
if (!formatOutput()) if (!formatOutput())
{ {
finishProtocol(FormatError); finishProtocol(FormatError);
@ -991,8 +996,10 @@ readCallback(StreamIoStatus status,
evalIn(); evalIn();
return 0; return 0;
} }
error("%s: No reply within %ld ms to \"%s\"\n", if (checkShouldPrint(ReplyTimeout)) {
name(), replyTimeout, outputLine.expand()()); error("%s: No reply within %ld ms to \"%s\"\n",
name(), replyTimeout, outputLine.expand()());
}
inputBuffer.clear(); inputBuffer.clear();
finishProtocol(ReplyTimeout); finishProtocol(ReplyTimeout);
return 0; return 0;
@ -1005,6 +1012,7 @@ readCallback(StreamIoStatus status,
finishProtocol(Fault); finishProtocol(Fault);
return 0; return 0;
} }
inputHook(input, size);
inputBuffer.append(input, size); inputBuffer.append(input, size);
debug("StreamCore::readCallback(%s) inputBuffer=\"%s\", size %" Z "u\n", debug("StreamCore::readCallback(%s) inputBuffer=\"%s\", size %" Z "u\n",
name(), inputBuffer.expand()(), inputBuffer.length()); name(), inputBuffer.expand()(), inputBuffer.length());
@ -1109,9 +1117,11 @@ readCallback(StreamIoStatus status,
} }
else else
{ {
error("%s: Timeout after reading %" Z "d byte%s \"%s%s\"\n", if (checkShouldPrint(ReadTimeout)) {
name(), end, end==1 ? "" : "s", end > 20 ? "..." : "", error("%s: Timeout after reading %" Z "d byte%s \"%s%s\"\n",
inputBuffer.expand(-20)()); name(), end, end==1 ? "" : "s", end > 20 ? "..." : "",
inputBuffer.expand(-20)());
}
} }
} }
@ -1122,7 +1132,7 @@ readCallback(StreamIoStatus status,
inputBuffer.remove(end + termlen); inputBuffer.remove(end + termlen);
if (inputBuffer) if (inputBuffer)
{ {
debug("StreamCore::readCallback(%s) unpared input left: \"%s\"\n", debug("StreamCore::readCallback(%s) unparsed input left: \"%s\"\n",
name(), inputBuffer.expand()()); name(), inputBuffer.expand()());
unparsedInput = true; unparsedInput = true;
} }
@ -1174,6 +1184,7 @@ matchInput()
char command; char command;
const char* fieldName = NULL; const char* fieldName = NULL;
StreamBuffer formatstring; StreamBuffer formatstring;
ssize_t delta = 0;
consumedInput = 0; consumedInput = 0;
@ -1208,7 +1219,7 @@ normal_format:
debug("StreamCore::matchInput(%s): format = \"%%%s\"\n", debug("StreamCore::matchInput(%s): format = \"%%%s\"\n",
name(), formatstring()); name(), formatstring());
if (fmt.flags & skip_flag || fmt.type == pseudo_format) if (fmt.flags & skip_flag || fmt.type == pseudo_format || fmt.type == needs_original_format)
{ {
long ldummy; long ldummy;
double ddummy; double ddummy;
@ -1230,9 +1241,20 @@ normal_format:
scanString(fmt, inputLine(consumedInput), NULL, size); scanString(fmt, inputLine(consumedInput), NULL, size);
break; break;
case pseudo_format: case pseudo_format:
// pass complete input // pass complete input line for scan and/or re-write
size = inputLine.length();
consumed = StreamFormatConverter::find(fmt.conv)-> consumed = StreamFormatConverter::find(fmt.conv)->
scanPseudo(fmt, inputLine, consumedInput); scanPseudo(fmt, inputLine, consumedInput);
delta += inputLine.length() - size; // track length changes
debug("after rewrite delta=%zi\n", delta);
break;
case needs_original_format:
// pass original input with adjusted current position
debug("before checksum delta=%zi\n", delta);
consumedInput -= delta; // correct for length changes
consumed = StreamFormatConverter::find(fmt.conv)->
scanPseudo(fmt, inputBuffer, consumedInput);
consumedInput += delta;
break; break;
default: default:
error("INTERNAL ERROR (%s): illegal format.type 0x%02x\n", error("INTERNAL ERROR (%s): illegal format.type 0x%02x\n",
@ -1823,4 +1845,37 @@ license(void)
"along with StreamDevice. If not, see https://www.gnu.org/licenses/.\n"; "along with StreamDevice. If not, see https://www.gnu.org/licenses/.\n";
} }
/**
* \brief Checks whether an error message should be printed based on the new error type.
*
* Will check based on the error type and the time since last error of the same type
* whether to print the new error. Also has a number of side effects such as counting
* up errors of each type and resetting the time since last error.
*
* \param[in] newErrorType the type of the new error
* \return true if the error should be preinted, else false
*/
bool StreamCore::checkShouldPrint(ProtocolResult newErrorType)
{
if (previousResult != newErrorType) {
previousResult = newErrorType;
numberOfErrors = 0;
time(&lastErrorTime);
return true;
}
else if ((int)(time(NULL) - lastErrorTime) > streamErrorDeadTime) {
time(&lastErrorTime);
if (numberOfErrors != 0) {
error("%s: %i additional errors of the following type seen in the last %i seconds\n",
name(), numberOfErrors, streamErrorDeadTime);
}
numberOfErrors = 0;
return true;
}
else {
numberOfErrors++;
return false;
}
}
#include "streamReferences" #include "streamReferences"

View File

@ -73,7 +73,7 @@ void acceptEvent(unsigned short mask, unsigned short timeout)
***************************************/ ***************************************/
#include "MacroMagic.h" #include "MacroMagic.h"
#include "time.h"
// Flags: 0x00FFFFFF reserved for StreamCore // Flags: 0x00FFFFFF reserved for StreamCore
const unsigned long None = 0x0000; const unsigned long None = 0x0000;
@ -95,6 +95,9 @@ const unsigned long ClearOnStart = InitRun|AsyncMode|GotValue|Aborted|
BusOwner|Separator|ScanTried| BusOwner|Separator|ScanTried|
AcceptInput|AcceptEvent|BusPending; AcceptInput|AcceptEvent|BusPending;
// The amount of time to wait before printing duplicated messages
extern int streamErrorDeadTime;
struct StreamFormat; struct StreamFormat;
class StreamCore : class StreamCore :
@ -173,6 +176,11 @@ protected:
ProtocolResult runningHandler; ProtocolResult runningHandler;
StreamBuffer fieldAddress; StreamBuffer fieldAddress;
// Keep track of errors to reduce logging frequencies
ProtocolResult previousResult;
time_t lastErrorTime;
int numberOfErrors;
StreamIoStatus lastInputStatus; StreamIoStatus lastInputStatus;
bool unparsedInput; bool unparsedInput;
@ -211,6 +219,7 @@ protected:
// virtual methods // virtual methods
virtual void protocolStartHook() {} virtual void protocolStartHook() {}
virtual void inputHook(const void* input, size_t size) {};
virtual void protocolFinishHook(ProtocolResult) {} virtual void protocolFinishHook(ProtocolResult) {}
virtual void startTimer(unsigned long timeout) = 0; virtual void startTimer(unsigned long timeout) = 0;
virtual bool formatValue(const StreamFormat&, const void* fieldaddress) = 0; virtual bool formatValue(const StreamFormat&, const void* fieldaddress) = 0;
@ -230,6 +239,7 @@ public:
private: private:
char* printCommands(StreamBuffer& buffer, const char* c); char* printCommands(StreamBuffer& buffer, const char* c);
bool checkShouldPrint(ProtocolResult newErrorType);
}; };
#endif #endif

View File

@ -21,8 +21,16 @@
*************************************************************************/ *************************************************************************/
#include <errno.h> #include <errno.h>
#include "StreamCore.h" #include <ctype.h>
#include "StreamError.h" #include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#if defined(vxWorks)
#include <symLib.h>
#include <sysSymTbl.h>
#endif
#include "epicsVersion.h" #include "epicsVersion.h"
#ifdef BASE_VERSION #ifdef BASE_VERSION
@ -30,10 +38,11 @@
#endif #endif
#ifdef EPICS_3_13 #ifdef EPICS_3_13
#include <semLib.h>
#include <wdLib.h>
#include <taskLib.h>
extern "C" { extern "C" {
static char* epicsStrDup(const char *s) { char* c = (char*)malloc(strlen(s)+1); strcpy(c, s); return c; }
#endif #endif
#define epicsAlarmGLOBAL #define epicsAlarmGLOBAL
@ -45,18 +54,11 @@ static char* epicsStrDup(const char *s) { char* c = (char*)malloc(strlen(s)+1);
#include "recGbl.h" #include "recGbl.h"
#include "devLib.h" #include "devLib.h"
#include "callback.h" #include "callback.h"
#include "initHooks.h"
#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>
#ifdef EPICS_3_13 #ifdef EPICS_3_13
static char* epicsStrDup(const char *s) { char* c = (char*)malloc(strlen(s)+1); strcpy(c, s); return c; }
#include <semLib.h>
#include <wdLib.h>
#include <taskLib.h>
extern DBBASE *pdbbase; extern DBBASE *pdbbase;
} // extern "C" } // extern "C"
#else // !EPICS_3_13 #else // !EPICS_3_13
@ -71,29 +73,25 @@ extern DBBASE *pdbbase;
#include "iocsh.h" #include "iocsh.h"
#include "epicsExport.h" #include "epicsExport.h"
#if defined(VERSION_INT) || EPICS_MODIFICATION >= 11
#include "initHooks.h"
#define WITH_IOC_RUN
#endif
#if !defined(VERSION_INT) && EPICS_MODIFICATION < 9 #if !defined(VERSION_INT) && EPICS_MODIFICATION < 9
// iocshCmd() is missing in iocsh.h (up to R3.14.8.2) // iocshCmd() is missing in iocsh.h (up to R3.14.8.2)
// To build with win32-x86, you MUST fix iocsh.h. // To build for Windows, you MUST fix iocsh.h.
// Move the declaration below to iocsh.h and rebuild base. // Move the declaration below to iocsh.h and rebuild base.
extern "C" epicsShareFunc int epicsShareAPI iocshCmd(const char *command); extern "C" epicsShareFunc int epicsShareAPI iocshCmd(const char *command);
#endif #endif
#endif // !EPICS_3_13 #endif // !EPICS_3_13
#if defined(__vxworks) || defined(vxWorks) #include "StreamCore.h"
#include <symLib.h> #include "StreamError.h"
#include <sysSymTbl.h>
#endif
#include "devStream.h" #include "devStream.h"
#define Z PRINTF_SIZE_T_PREFIX #define Z PRINTF_SIZE_T_PREFIX
#if defined(VERSION_INT) || EPICS_MODIFICATION >= 11
#define WITH_IOC_RUN
#endif
// More flags: 0x00FFFFFF used by StreamCore // More flags: 0x00FFFFFF used by StreamCore
const unsigned long InDestructor = 0x0100000; const unsigned long InDestructor = 0x0100000;
const unsigned long ValueReceived = 0x0200000; const unsigned long ValueReceived = 0x0200000;
@ -139,6 +137,7 @@ class Stream : protected StreamCore
#endif #endif
// StreamCore methods // StreamCore methods
void inputHook(const void* input, size_t size);
void protocolFinishHook(ProtocolResult); void protocolFinishHook(ProtocolResult);
void startTimer(unsigned long timeout); void startTimer(unsigned long timeout);
bool getFieldAddress(const char* fieldname, bool getFieldAddress(const char* fieldname,
@ -170,10 +169,7 @@ class Stream : protected StreamCore
bool print(format_t *format, va_list ap); bool print(format_t *format, va_list ap);
ssize_t scan(format_t *format, void* pvalue, size_t maxStringSize); ssize_t scan(format_t *format, void* pvalue, size_t maxStringSize);
bool process(); bool process();
#ifdef WITH_IOC_RUN
static void initHook(initHookState); static void initHook(initHookState);
#endif
// device support functions // device support functions
friend long streamInitRecord(dbCommon *record, const struct link *ioLink, friend long streamInitRecord(dbCommon *record, const struct link *ioLink,
@ -198,6 +194,9 @@ public:
extern "C" { // needed for Windows extern "C" { // needed for Windows
epicsExportAddress(int, streamDebug); epicsExportAddress(int, streamDebug);
epicsExportAddress(int, streamError); epicsExportAddress(int, streamError);
epicsExportAddress(int, streamDebugColored);
epicsExportAddress(int, streamErrorDeadTime);
epicsExportAddress(int, streamMsgTimeStamped);
} }
// for subroutine record // for subroutine record
@ -330,7 +329,6 @@ epicsExportAddress(drvet, stream);
#ifdef EPICS_3_13 #ifdef EPICS_3_13
void streamEpicsPrintTimestamp(char* buffer, size_t size) void streamEpicsPrintTimestamp(char* buffer, size_t size)
{ {
size_t tlen;
char* c; char* c;
TS_STAMP tm; TS_STAMP tm;
tsLocalTime (&tm); tsLocalTime (&tm);
@ -339,16 +337,17 @@ void streamEpicsPrintTimestamp(char* buffer, size_t size)
if (c) { if (c) {
c[4] = 0; c[4] = 0;
} }
tlen = strlen(buffer); }
sprintf(buffer+tlen, " %.*s", (int)(size-tlen-2), taskName(0));
static const char* epicsThreadGetNameSelf()
{
return taskName(0);
} }
#else // !EPICS_3_13 #else // !EPICS_3_13
void streamEpicsPrintTimestamp(char* buffer, size_t size) void streamEpicsPrintTimestamp(char* buffer, size_t size)
{ {
size_t tlen;
epicsTime tm = epicsTime::getCurrent(); epicsTime tm = epicsTime::getCurrent();
tlen = tm.strftime(buffer, size, "%Y/%m/%d %H:%M:%S.%06f"); tm.strftime(buffer, size, "%Y/%m/%d %H:%M:%S.%06f");
sprintf(buffer+tlen, " %.*s", (int)(size-tlen-2), epicsThreadGetNameSelf());
} }
#endif // !EPICS_3_13 #endif // !EPICS_3_13
@ -438,13 +437,21 @@ long streamReportRecord(const char* recordname)
return OK; return OK;
} }
#if defined(_WIN32) && !defined(_WIN64)
static const char* epicsThreadGetNameSelfWrapper(void)
{
return epicsThreadGetNameSelf();
}
#define epicsThreadGetNameSelf epicsThreadGetNameSelfWrapper
#endif
long Stream:: long Stream::
drvInit() drvInit()
{ {
char* path; char* path;
debug("drvStreamInit()\n"); debug("drvStreamInit()\n");
path = getenv("STREAM_PROTOCOL_PATH"); path = getenv("STREAM_PROTOCOL_PATH");
#if defined(__vxworks) || defined(vxWorks) #if defined(vxWorks)
// for compatibility reasons look for global symbols // for compatibility reasons look for global symbols
if (!path) if (!path)
{ {
@ -461,54 +468,87 @@ drvInit()
#endif #endif
if (!path) if (!path)
fprintf(stderr, fprintf(stderr,
"drvStreamInit: Warning! STREAM_PROTOCOL_PATH not set. " "drvStreamInit: Warning! STREAM_PROTOCOL_PATH not set.\n");
"Defaults to \"%s\"\n", StreamProtocolParser::path);
else else
StreamProtocolParser::path = path; StreamProtocolParser::path = path;
debug("StreamProtocolParser::path = %s\n", debug("StreamProtocolParser::path = %s\n",
StreamProtocolParser::path); StreamProtocolParser::path);
StreamPrintTimestampFunction = streamEpicsPrintTimestamp; StreamPrintTimestampFunction = streamEpicsPrintTimestamp;
StreamGetThreadNameFunction = epicsThreadGetNameSelf;
#ifdef WITH_IOC_RUN
initHookRegister(initHook); initHookRegister(initHook);
#endif
return OK; return OK;
} }
#ifdef WITH_IOC_RUN
void Stream:: void Stream::
initHook(initHookState state) initHook(initHookState state)
{ {
Stream* stream; Stream* stream;
if (state == initHookAtIocRun) switch (state) {
{ #ifdef WITH_IOC_RUN
debug("Stream::initHook(initHookAtIocRun) interruptAccept=%d\n", interruptAccept); case initHookAtIocRun:
static int inIocInit = 1;
if (inIocInit)
{ {
// We don't want to run @init twice in iocInit // re-run @init handlers after restart
inIocInit = 0; static int inIocInit = 1;
return; if (inIocInit)
}
for (stream = static_cast<Stream*>(first); stream;
stream = static_cast<Stream*>(stream->next))
{
if (!stream->onInit) continue;
debug("Stream::initHook(initHookAtIocRun) Re-inititializing %s\n", stream->name());
if (!stream->startProtocol(StartInit))
{ {
error("%s: Re-initialization failed.\n", // We don't want to run @init twice in iocInit
stream->name()); inIocInit = 0;
return;
} }
stream->initDone.wait();
for (stream = static_cast<Stream*>(first); stream;
stream = static_cast<Stream*>(stream->next))
{
if (!stream->onInit) continue;
debug("%s: running @init handler\n", stream->name());
if (!stream->startProtocol(StartInit))
{
error("%s: @init handler failed.\n",
stream->name());
}
stream->initDone.wait();
}
break;
} }
case initHookAtIocPause:
{
// stop polling I/O Intr
for (stream = static_cast<Stream*>(first); stream;
stream = static_cast<Stream*>(stream->next))
{
if (stream->record->scan == SCAN_IO_EVENT) {
debug("%s: stopping \"I/O Intr\"\n", stream->name());
stream->finishProtocol(Stream::Abort);
}
}
break;
}
case initHookAfterIocRunning:
#else
case initHookAfterInterruptAccept:
#endif
{
// start polling I/O Intr
for (stream = static_cast<Stream*>(first); stream;
stream = static_cast<Stream*>(stream->next))
{
if (stream->record->scan == SCAN_IO_EVENT) {
debug("%s: starting \"I/O Intr\"\n", stream->name());
if (!stream->startProtocol(StartAsync))
{
error("%s: Can't start \"I/O Intr\" protocol\n",
stream->name());
}
}
}
break;
}
default:
break;
} }
} }
#endif
// device support (C interface) ////////////////////////////////////////// // device support (C interface) //////////////////////////////////////////
@ -622,7 +662,16 @@ long streamGetIointInfo(int cmd, dbCommon *record, IOSCANPVT *ppvt)
*ppvt = stream->ioscanpvt; *ppvt = stream->ioscanpvt;
if (cmd == 0) if (cmd == 0)
{ {
debug("streamGetIointInfo: starting protocol\n"); #ifdef WITH_IOC_RUN
if (!interruptAccept) {
// We will start polling later in initHook.
debug("streamGetIointInfo(%s): start later...\n",
record->name);
return OK;
}
#endif
debug("streamGetIointInfo(%s): start protocol\n",
record->name);
// SCAN has been set to "I/O Intr" // SCAN has been set to "I/O Intr"
if (!stream->startProtocol(Stream::StartAsync)) if (!stream->startProtocol(Stream::StartAsync))
{ {
@ -633,6 +682,8 @@ long streamGetIointInfo(int cmd, dbCommon *record, IOSCANPVT *ppvt)
else else
{ {
// SCAN is no longer "I/O Intr" // SCAN is no longer "I/O Intr"
debug("streamGetIointInfo(%s): abort protocol\n",
record->name);
stream->finishProtocol(Stream::Abort); stream->finishProtocol(Stream::Abort);
} }
return OK; return OK;
@ -884,6 +935,10 @@ process()
debug("Stream::process(%s) start\n", name()); debug("Stream::process(%s) start\n", name());
status = NO_ALARM; status = NO_ALARM;
convert = OK; convert = OK;
if (record->tpro)
{
StreamDebugClass(record->name).print("start protocol '%s'\n", protocolname());
}
if (!startProtocol(record->proc == 2 ? StreamCore::StartInit : StreamCore::StartNormal)) if (!startProtocol(record->proc == 2 ? StreamCore::StartInit : StreamCore::StartNormal))
{ {
debug("Stream::process(%s): could not start %sprotocol, status=%s (%d)\n", debug("Stream::process(%s): could not start %sprotocol, status=%s (%d)\n",
@ -978,11 +1033,26 @@ expire(const epicsTime&)
// StreamCore virtual methods //////////////////////////////////////////// // StreamCore virtual methods ////////////////////////////////////////////
void Stream::
inputHook(const void* input, size_t size)
{
if (record->tpro > 1)
{
StreamDebugClass(record->name).print("received \"%s\"\n",
StreamBuffer(input, size).expand()());
}
}
void Stream:: void Stream::
protocolFinishHook(ProtocolResult result) protocolFinishHook(ProtocolResult result)
{ {
debug("Stream::protocolFinishHook(%s, %s)\n", debug("Stream::protocolFinishHook(%s, %s)\n",
name(), toStr(result)); name(), toStr(result));
if (record->tpro)
{
StreamDebugClass(record->name).print("%s. out=\"%s\", in=\"%s\"\n",
toStr(result), outputLine.expand()(), inputLine.expand()());
}
switch (result) switch (result)
{ {
case Success: case Success:

View File

@ -20,18 +20,26 @@
* along with StreamDevice. If not, see https://www.gnu.org/licenses/. * along with StreamDevice. If not, see https://www.gnu.org/licenses/.
*************************************************************************/ *************************************************************************/
#include "StreamError.h"
#ifdef _WIN32
#include <windows.h>
#endif
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include <stdio.h> #include <stdio.h>
#ifdef _WIN32
#include <windows.h>
#include <io.h>
#else
#include <unistd.h>
#endif /* _WIN32 */
#include "StreamError.h"
int streamDebug = 0; int streamDebug = 0;
int streamError = 1; int streamError = 1;
FILE *StreamDebugFile = NULL; FILE *StreamDebugFile = NULL;
/*0: disable timestamps on stream messages (both debug and error)*/
int streamMsgTimeStamped = 1;
#ifndef va_copy #ifndef va_copy
#ifdef __va_copy #ifdef __va_copy
#define va_copy __va_copy #define va_copy __va_copy
@ -46,18 +54,36 @@ FILE *StreamDebugFile = NULL;
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif #endif
/* Enable ANSI colors in Windows console */ /* Enable ANSI color support in Windows console */
static int win_console_init() { static bool win_console_init() {
DWORD dwMode = 0; HANDLE hCons[] = { GetStdHandle(STD_ERROR_HANDLE),
HANDLE hCons = GetStdHandle(STD_ERROR_HANDLE); GetStdHandle(STD_OUTPUT_HANDLE) };
GetConsoleMode(hCons, &dwMode); for(int i=0; i < sizeof(hCons) / sizeof(HANDLE); ++i)
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; {
SetConsoleMode(hCons, dwMode); DWORD dwMode = 0;
return 0; if (hCons[i] == NULL ||
hCons[i] == INVALID_HANDLE_VALUE ||
!GetConsoleMode(hCons[i], &dwMode))
{
return false;
}
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if (!SetConsoleMode(hCons[i], dwMode))
{
return false;
}
}
return true;
} }
static int s = win_console_init();
#endif /* do isatty() call second as always want to run win_console_init() */
int streamDebugColored = win_console_init() && _isatty(_fileno(stderr));
#else
int streamDebugColored = isatty(fileno(stderr));
#endif /* _WIN32 */
/* You can globally change the printTimestamp function /* You can globally change the printTimestamp function
by setting the StreamPrintTimestampFunction variable by setting the StreamPrintTimestampFunction variable
@ -73,6 +99,7 @@ static void printTimestamp(char* buffer, size_t size)
} }
void (*StreamPrintTimestampFunction)(char* buffer, size_t size) = printTimestamp; void (*StreamPrintTimestampFunction)(char* buffer, size_t size) = printTimestamp;
const char* (*StreamGetThreadNameFunction)(void) = NULL;
void StreamError(const char* fmt, ...) void StreamError(const char* fmt, ...)
{ {
@ -92,44 +119,88 @@ void StreamError(int line, const char* file, const char* fmt, ...)
void StreamVError(int line, const char* file, const char* fmt, va_list args) void StreamVError(int line, const char* file, const char* fmt, va_list args)
{ {
char timestamp[40];
if (!(streamError || streamDebug)) return; // Error logging disabled if (!(streamError || streamDebug)) return; // Error logging disabled
StreamPrintTimestampFunction(timestamp, 40); char timestamp[40];
const char *threadname = NULL;
int timeStamped = streamMsgTimeStamped;
if (timeStamped)
{
StreamPrintTimestampFunction(timestamp, sizeof(timestamp));
}
if (StreamGetThreadNameFunction)
{
threadname = StreamGetThreadNameFunction();
}
#ifdef va_copy #ifdef va_copy
if (StreamDebugFile) if (StreamDebugFile)
{ {
va_list args2; va_list args2;
va_copy(args2, args); va_copy(args2, args);
fprintf(StreamDebugFile, "%s ", timestamp); if (timeStamped)
{
fprintf(StreamDebugFile, "%s ", timestamp);
}
if (threadname)
{
fprintf(StreamDebugFile, "%s ", threadname);
}
vfprintf(StreamDebugFile, fmt, args2); vfprintf(StreamDebugFile, fmt, args2);
fflush(StreamDebugFile); fflush(StreamDebugFile);
va_end(args2); va_end(args2);
} }
#endif #endif
fprintf(stderr, "\033[31;1m"); fprintf(stderr, "%s", ansiEscape(ANSI_RED_BOLD));
fprintf(stderr, "%s ", timestamp); if (timeStamped)
{
fprintf(stderr, "%s ", timestamp);
}
if (threadname)
{
fprintf(stderr, "%s ", threadname);
}
if (file) if (file)
{ {
fprintf(stderr, "%s line %d: ", file, line); fprintf(stderr, "%s line %d: ", file, line);
} }
vfprintf(stderr, fmt, args); vfprintf(stderr, fmt, args);
fprintf(stderr, "\033[0m"); fprintf(stderr, "%s", ansiEscape(ANSI_RESET));
} }
int StreamDebugClass:: int StreamDebugClass::
print(const char* fmt, ...) print(const char* fmt, ...)
{ {
va_list args; va_list args;
char timestamp[40];
StreamPrintTimestampFunction(timestamp, 40);
va_start(args, fmt); va_start(args, fmt);
const char* f = strrchr(file, '/');
if (f) f++; else f = file;
FILE* fp = StreamDebugFile ? StreamDebugFile : stderr; FILE* fp = StreamDebugFile ? StreamDebugFile : stderr;
fprintf(fp, "%s ", timestamp); if (streamMsgTimeStamped)
fprintf(fp, "%s:%d: ", f, line); {
char timestamp[40];
StreamPrintTimestampFunction(timestamp, sizeof(timestamp));
fprintf(fp, "%s ", timestamp);
}
if (StreamGetThreadNameFunction)
{
fprintf(fp, "%s ", StreamGetThreadNameFunction());
}
if (file) {
const char* f = strrchr(file, '/');
if (f) f++; else f = file;
fprintf(fp, "%s:", f);
if (line) fprintf(fp, "%d:", line);
fprintf(fp, " ");
}
vfprintf(fp, fmt, args); vfprintf(fp, fmt, args);
fflush(fp); fflush(fp);
va_end(args); va_end(args);
return 1; return 1;
} }
/**
* Return an ANSI escape code if coloured debug output is enabled
*/
const char* ansiEscape(AnsiMode mode)
{
static const char* AnsiEscapes[] = { "\033[7m", "\033[27m", "\033[47m",
"\033[0m", "\033[31;1m" };
return streamDebugColored ? AnsiEscapes[mode] : "";
}

View File

@ -32,7 +32,10 @@
extern int streamDebug; extern int streamDebug;
extern int streamError; extern int streamError;
extern int streamDebugColored;
extern int streamMsgTimeStamped;
extern void (*StreamPrintTimestampFunction)(char* buffer, size_t size); extern void (*StreamPrintTimestampFunction)(char* buffer, size_t size);
extern const char* (*StreamGetThreadNameFunction)(void);
void StreamError(int line, const char* file, const char* fmt, ...) void StreamError(int line, const char* file, const char* fmt, ...)
__attribute__((__format__(__printf__,3,4))); __attribute__((__format__(__printf__,3,4)));
@ -53,17 +56,21 @@ class StreamDebugClass
const char* file; const char* file;
int line; int line;
public: public:
StreamDebugClass(const char* file, int line) : StreamDebugClass(const char* file = NULL, int line = 0) :
file(file), line(line) {} file(file), line(line) {}
int print(const char* fmt, ...) int print(const char* fmt, ...)
__attribute__((__format__(__printf__,2,3))); __attribute__((__format__(__printf__,2,3)));
}; };
inline StreamDebugClass
StreamDebugObject(const char* file, int line)
{ return StreamDebugClass(file, line); }
#define error StreamError #define error StreamError
#define debug (!streamDebug)?0:StreamDebugObject(__FILE__,__LINE__).print #define debug (!streamDebug)?0:StreamDebugClass(__FILE__,__LINE__).print
#define debug2 (streamDebug<2)?0:StreamDebugClass(__FILE__,__LINE__).print
/*
* ANSI escape sequences for terminal output
*/
enum AnsiMode { ANSI_REVERSE_VIDEO, ANSI_NOT_REVERSE_VIDEO, ANSI_BG_WHITE,
ANSI_RESET, ANSI_RED_BOLD };
extern const char* ansiEscape(AnsiMode mode);
#endif #endif

View File

@ -42,7 +42,8 @@ typedef enum {
enum_format, enum_format,
double_format, double_format,
string_format, string_format,
pseudo_format pseudo_format,
needs_original_format
} StreamFormatType; } StreamFormatType;
extern const char* StreamFormatTypeStr[]; extern const char* StreamFormatTypeStr[];

View File

@ -24,6 +24,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <ctype.h> #include <ctype.h>
#include <limits.h> #include <limits.h>
#include "StreamFormatConverter.h" #include "StreamFormatConverter.h"
#include "StreamError.h" #include "StreamError.h"
@ -148,8 +149,6 @@ parseFormat(const char*& source, FormatType formatType, StreamFormat& streamForm
error("Missing converter character\n"); error("Missing converter character\n");
return false; return false;
} }
debug("StreamFormatConverter::parseFormat: converter='%c'\n",
streamFormat.conv);
StreamFormatConverter* converter; StreamFormatConverter* converter;
converter = StreamFormatConverter::find(streamFormat.conv); converter = StreamFormatConverter::find(streamFormat.conv);
if (!converter) if (!converter)

View File

@ -23,6 +23,7 @@
#include <ctype.h> #include <ctype.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdarg.h> #include <stdarg.h>
#include "StreamProtocol.h" #include "StreamProtocol.h"
#include "StreamFormatConverter.h" #include "StreamFormatConverter.h"
#include "StreamError.h" #include "StreamError.h"
@ -52,7 +53,7 @@ class StreamProtocolParser::Protocol::Variable
// StreamProtocolParser // StreamProtocolParser
StreamProtocolParser* StreamProtocolParser::parsers = NULL; StreamProtocolParser* StreamProtocolParser::parsers = NULL;
const char* StreamProtocolParser::path = "."; const char* StreamProtocolParser::path = NULL;
static const char* specialChars = " ,;{}=()$'\"+-*/"; static const char* specialChars = " ,;{}=()$'\"+-*/";
// Client destructor // Client destructor
@ -156,52 +157,70 @@ this after protocol arguments have been replaced.
StreamProtocolParser* StreamProtocolParser:: StreamProtocolParser* StreamProtocolParser::
readFile(const char* filename) readFile(const char* filename)
{ {
FILE* file; FILE* file = NULL;
#ifdef _WIN32
const char pathseparator = ';';
const char dirseparator = '\\';
#else
const char pathseparator = ':';
const char dirseparator = '/';
#endif
StreamProtocolParser* parser; StreamProtocolParser* parser;
const char *p; const char *p;
const char *s;
size_t n; size_t n;
StreamBuffer dir; StreamBuffer dir;
// look for filename in every dir in search path // no path or absolute file name
for (p = path; *p; p += n) if (!path || filename[0] == '/'
{ #ifdef _WIN32
dir.clear(); || filename[0] == '\\' || (isalpha(filename[0]) && filename[1] == ':')
// get next dir from search path (avoiding strtok, strsep, strcspn) #endif
s = strchr(p, pathseparator); ) {
if (s) n = s - p; // absolute file name
else n = strlen(p); file = fopen(filename, "r");
dir.append(p, n); if (file) {
if (n && p[n-1] != dirseparator) dir.append(dirseparator); debug("StreamProtocolParser::readFile: found '%s'\n", filename);
if (s) p++; } else {
// append filename error("Can't find readable file '%s'\n", filename);
dir.append(filename); return NULL;
// try to read the file }
debug("StreamProtocolParser::readFile: try '%s'\n", dir()); } else {
file = fopen(dir(), "r"); // look for filename in every dir in search path
if (file) for (p = path; *p; p += n)
{ {
// file found; create a parser to read it dir.clear();
parser = new StreamProtocolParser(file, filename); // allow ':' or ';' for OS independence
fclose(file); // we need to be careful with drive letters though
if (!parser->valid) return NULL; n = strcspn(p, ":;");
// printf( #ifdef _WIN32
// "/---------------------------------------------------------------------\\\n"); if (n == 1 && p[1] == ':' && isalpha(p[0]))
// parser->report(); {
// printf( // driver letter
// "\\---------------------------------------------------------------------/\n"); n = 2 + strcspn(p+2, ":;");
return parser; }
#endif
dir.append(p, n);
// append / after everything except empty path [or drive letter]
// Windows is fine with / as well
if (n) {
#ifdef _WIN32
if (n != 2 || p[1] != ':' || !isalpha(p[0]))
#endif
dir.append('/');
}
if (p[n]) n++; // skip the path separator
dir.append(filename);
// try to read the file
debug("StreamProtocolParser::readFile: try '%s'\n", dir());
file = fopen(dir(), "r");
if (file) {
debug("StreamProtocolParser::readFile: found '%s'\n", dir());
break;
}
}
if (!file) {
error("Can't find readable file '%s' in '%s'\n", filename, path);
return NULL;
} }
} }
error("Can't find readable file '%s' in '%s'\n", filename, path); // file found; create a parser to read it
return NULL; parser = new StreamProtocolParser(file, filename);
fclose(file);
if (!parser->valid) return NULL;
return parser;
} }
/* /*
@ -262,7 +281,7 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
startline = line; startline = line;
if (!readToken(token, " ,;{}=()$'\"", isGlobalContext(commands))) if (!readToken(token, " ,;{}=()$'\"", isGlobalContext(commands)))
return false; return false;
debug("StreamProtocolParser::parseProtocol:" debug2("StreamProtocolParser::parseProtocol:"
" token='%s'\n", token.expand()()); " token='%s'\n", token.expand()());
if (token[0] == 0) if (token[0] == 0)
@ -413,7 +432,7 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
error(line, filename(), "after command '%s'\n", token()); error(line, filename(), "after command '%s'\n", token());
return false; return false;
} }
debug("parseProtocol: command '%s'\n", (*commands).expand()()); debug2("parseProtocol: command '%s'\n", (*commands).expand()());
commands->append('\0'); // terminate value with null byte commands->append('\0'); // terminate value with null byte
} }
} }
@ -468,7 +487,7 @@ Each time newline is read, line is incremented.
if (c == '$') if (c == '$')
{ {
// a variable // a variable
debug("StreamProtocolParser::readToken: Variable\n"); debug2("StreamProtocolParser::readToken: Variable\n");
buffer.append(c); buffer.append(c);
if (quote) buffer.append('"'); // mark as quoted variable if (quote) buffer.append('"'); // mark as quoted variable
c = getc(file); c = getc(file);
@ -485,7 +504,7 @@ Each time newline is read, line is incremented.
quote = false; quote = false;
// variable like ${xyz} // variable like ${xyz}
if (!readToken(buffer, "{}=;")) return false; if (!readToken(buffer, "{}=;")) return false;
debug("StreamProtocolParser::readToken: Variable '%s' in {}\n", debug2("StreamProtocolParser::readToken: Variable '%s' in {}\n",
buffer(token)); buffer(token));
c = getc(file); c = getc(file);
if (c != '}') if (c != '}')
@ -511,7 +530,7 @@ Each time newline is read, line is incremented.
} }
else if (quote || c == '\'' || c == '"') else if (quote || c == '\'' || c == '"')
{ {
debug("StreamProtocolParser::readToken: Quoted string\n"); debug2("StreamProtocolParser::readToken: Quoted string\n");
// quoted string // quoted string
if (!quote) if (!quote)
{ {
@ -570,13 +589,13 @@ Each time newline is read, line is incremented.
} }
else if (strchr (specialchars, c)) else if (strchr (specialchars, c))
{ {
debug("StreamProtocolParser::readToken: Special '%c'\n", c); debug2("StreamProtocolParser::readToken: Special '%c'\n", c);
// a single character // a single character
buffer.append(c); buffer.append(c);
return true; return true;
} }
// a word or (variable name) // a word or (variable name)
debug("StreamProtocolParser::readToken: word\n"); debug2("StreamProtocolParser::readToken: word\n");
while (1) while (1)
{ {
buffer.append(tolower(c)); buffer.append(tolower(c));
@ -587,7 +606,7 @@ Each time newline is read, line is incremented.
break; break;
} }
} }
debug("StreamProtocolParser::readToken: word='%s' c='%c'\n", debug2("StreamProtocolParser::readToken: word='%s' c='%c'\n",
buffer(token), c); buffer(token), c);
buffer.append('\0').append(&l, sizeof(l)); // append line number buffer.append('\0').append(&l, sizeof(l)); // append line number
return true; return true;
@ -615,7 +634,7 @@ parseValue(StreamBuffer& buffer, bool lazy)
{ {
token = buffer.length(); // start of next token token = buffer.length(); // start of next token
if (!readToken(buffer)) return false; if (!readToken(buffer)) return false;
debug("StreamProtocolParser::parseValue:%d: %s\n", debug2("StreamProtocolParser::parseValue:%d: %s\n",
line, buffer.expand(token)()); line, buffer.expand(token)());
c = buffer[token]; c = buffer[token];
if (c == '$') // a variable if (c == '$') // a variable
@ -775,7 +794,7 @@ Protocol(const Protocol& p, StreamBuffer& name, int _line)
parameter[0] = protocolname(); parameter[0] = protocolname();
for (i = 0; i < 9; i++) for (i = 0; i < 9; i++)
{ {
debug("StreamProtocolParser::Protocol::Protocol $%d=\"%s\"\n", if (i) debug("StreamProtocolParser::Protocol::Protocol $%d=\"%s\"\n",
i, parameter[i]); i, parameter[i]);
nextparameter = parameter[i] + strlen(parameter[i]) + 1; nextparameter = parameter[i] + strlen(parameter[i]) + 1;
if (nextparameter > name.length() + parameter[0]) break; if (nextparameter > name.length() + parameter[0]) break;
@ -928,7 +947,7 @@ getCommands(const char* handlername, StreamBuffer& code, Client* client)
if (!pvar) return true; if (!pvar) return true;
if (!pvar->value) return true; if (!pvar->value) return true;
const char* source = pvar->value(); const char* source = pvar->value();
debug("StreamProtocolParser::Protocol::getCommands" debug2("StreamProtocolParser::Protocol::getCommands"
"(handlername=\"%s\", client=\"%s\"): source=%s\n", "(handlername=\"%s\", client=\"%s\"): source=%s\n",
handlername, client->name(), pvar->value.expand()()); handlername, client->name(), pvar->value.expand()());
if (!compileCommands(code, source, client)) if (!compileCommands(code, source, client))
@ -945,15 +964,15 @@ getCommands(const char* handlername, StreamBuffer& code, Client* client)
"in protocol '%s'\n", protocolname()); "in protocol '%s'\n", protocolname());
return false; return false;
} }
debug("commands %s: %s\n", handlername, pvar->value.expand()()); debug2("commands %s: %s\n", handlername, pvar->value.expand()());
debug("compiled to: %s\n", code.expand()()); debug2("compiled to: %s\n", code.expand()());
return true; return true;
} }
bool StreamProtocolParser::Protocol:: bool StreamProtocolParser::Protocol::
replaceVariable(StreamBuffer& buffer, const char* varname) replaceVariable(StreamBuffer& buffer, const char* varname)
{ {
debug("StreamProtocolParser::Protocol::replaceVariable %s\n", varname); debug2("StreamProtocolParser::Protocol::replaceVariable %s\n", varname);
bool quoted = false; bool quoted = false;
if (*++varname == '"') if (*++varname == '"')
{ {
@ -1033,16 +1052,16 @@ compileNumber(unsigned long& number, const char*& source, unsigned long max)
unsigned long n; unsigned long n;
StreamBuffer buffer; StreamBuffer buffer;
debug("StreamProtocolParser::Protocol::compileNumber source=\"%s\"\n", debug2("StreamProtocolParser::Protocol::compileNumber source=\"%s\"\n",
source); source);
while (*source == '$' || (*source >= '0' && *source <= '9')) while (*source == '$' || (*source >= '0' && *source <= '9'))
{ {
debug("StreamProtocolParser::Protocol::compileNumber *source=%u source=\"%s\"\n", debug2("StreamProtocolParser::Protocol::compileNumber *source=%u source=\"%s\"\n",
*source, source); *source, source);
if (*source == '$') if (*source == '$')
{ {
if (!replaceVariable(buffer, source)) return false; if (!replaceVariable(buffer, source)) return false;
debug("buffer=%s\n", buffer.expand()()); debug2("buffer=%s\n", buffer.expand()());
buffer.truncate(-1-(int)sizeof(int)); buffer.truncate(-1-(int)sizeof(int));
} }
else else
@ -1077,7 +1096,7 @@ compileNumber(unsigned long& number, const char*& source, unsigned long max)
return false; return false;
} }
number = n; number = n;
debug("StreamProtocolParser::Protocol::compileNumber %s = %ld\n", debug2("StreamProtocolParser::Protocol::compileNumber %s = %ld\n",
buffer(), n); buffer(), n);
return true; return true;
} }
@ -1095,7 +1114,7 @@ compileString(StreamBuffer& buffer, const char*& source,
size_t formatpos = buffer.length(); size_t formatpos = buffer.length();
line = getLineNumber(source); line = getLineNumber(source);
debug("StreamProtocolParser::Protocol::compileString " debug2("StreamProtocolParser::Protocol::compileString "
"line %d source=\"%s\" formatType=%s quoted=%i recursionDepth=%d\n", "line %d source=\"%s\" formatType=%s quoted=%i recursionDepth=%d\n",
line, source, ::toStr(formatType), quoted, recursionDepth); line, source, ::toStr(formatType), quoted, recursionDepth);
@ -1110,7 +1129,7 @@ compileString(StreamBuffer& buffer, const char*& source,
// this is step 2: replacing the formats // this is step 2: replacing the formats
if (!*source || (newline = getLineNumber(source)) != line) if (!*source || (newline = getLineNumber(source)) != line)
{ {
debug("StreamProtocolParser::Protocol::compileString line %i: %s\n", line, buffer.expand()()); debug2("StreamProtocolParser::Protocol::compileString line %i: %s\n", line, buffer.expand()());
// compile all formats in this line // compile all formats in this line
// We do this here after all variables in this line // We do this here after all variables in this line
// have been replaced and after string has been coded. // have been replaced and after string has been coded.
@ -1133,7 +1152,7 @@ compileString(StreamBuffer& buffer, const char*& source,
formatpos+=2; formatpos+=2;
continue; continue;
} }
debug("StreamProtocolParser::Protocol::compileString " debug2("StreamProtocolParser::Protocol::compileString "
"format=\"%s\"\n", buffer.expand(formatpos)()); "format=\"%s\"\n", buffer.expand(formatpos)());
nformats++; nformats++;
formatbuffer.clear(); formatbuffer.clear();
@ -1150,14 +1169,14 @@ compileString(StreamBuffer& buffer, const char*& source,
} }
size_t formatlen = p - buffer(formatpos); size_t formatlen = p - buffer(formatpos);
buffer.replace(formatpos, formatlen, formatbuffer); buffer.replace(formatpos, formatlen, formatbuffer);
debug("StreamProtocolParser::Protocol::compileString " debug2("StreamProtocolParser::Protocol::compileString "
"replaced by: \"%s\"\n", buffer.expand(formatpos)()); "replaced by: \"%s\"\n", buffer.expand(formatpos)());
formatpos += formatbuffer.length(); formatpos += formatbuffer.length();
continue; continue;
} }
formatpos ++; formatpos ++;
} }
debug("StreamProtocolParser::Protocol::compileString " debug2("StreamProtocolParser::Protocol::compileString "
"%d formats found in line %d\n", nformats, line); "%d formats found in line %d\n", nformats, line);
} }
if (!*source) break; if (!*source) break;
@ -1400,7 +1419,7 @@ compileString(StreamBuffer& buffer, const char*& source,
"Unexpected '%s' in string\n", source); "Unexpected '%s' in string\n", source);
return false; return false;
} }
debug("StreamProtocolParser::Protocol::compileString buffer=%s\n", buffer.expand()()); debug2("StreamProtocolParser::Protocol::compileString buffer=%s\n", buffer.expand()());
return true; return true;
} }
@ -1446,7 +1465,7 @@ compileFormat(StreamBuffer& buffer, const char*& formatstr,
// add fieldname for debug purpose // add fieldname for debug purpose
fieldname=buffer.length(); fieldname=buffer.length();
buffer.append(source, fieldnameEnd - source).append(eos); buffer.append(source, fieldnameEnd - source).append(eos);
debug("StreamProtocolParser::Protocol::compileFormat: fieldname='%s'\n", debug2("StreamProtocolParser::Protocol::compileFormat: fieldname='%s'\n",
buffer(fieldname)); buffer(fieldname));
StreamBuffer fieldAddress; StreamBuffer fieldAddress;
if (!client->getFieldAddress(buffer(fieldname), fieldAddress)) if (!client->getFieldAddress(buffer(fieldname), fieldAddress))
@ -1507,14 +1526,14 @@ compileFormat(StreamBuffer& buffer, const char*& formatstr,
// add formatstr for debug purpose // add formatstr for debug purpose
buffer.append(formatstart, source-formatstart).append(eos); buffer.append(formatstart, source-formatstart).append(eos);
debug("StreamProtocolParser::Protocol::compileFormat: formatstring=\"%s\"\n", debug2("StreamProtocolParser::Protocol::compileFormat: formatstring=\"%s\"\n",
StreamBuffer(formatstart, source-formatstart).expand()()); StreamBuffer(formatstart, source-formatstart).expand()());
// add streamFormat structure and info // add streamFormat structure and info
buffer.append(&streamFormat, sizeof(streamFormat)); buffer.append(&streamFormat, sizeof(streamFormat));
buffer.append(infoString); buffer.append(infoString);
debug("StreamProtocolParser::Protocol::compileFormat: format.type=%s, " debug2("StreamProtocolParser::Protocol::compileFormat: format.type=%s, "
"infolen=%ld infostring=\"%s\"\n", "infolen=%ld infostring=\"%s\"\n",
StreamFormatTypeStr[streamFormat.type], StreamFormatTypeStr[streamFormat.type],
streamFormat.infolen, infoString.expand()()); streamFormat.infolen, infoString.expand()());

View File

@ -20,14 +20,14 @@
* along with StreamDevice. If not, see https://www.gnu.org/licenses/. * along with StreamDevice. If not, see https://www.gnu.org/licenses/.
*************************************************************************/ *************************************************************************/
#include "StreamFormatConverter.h"
#include "StreamError.h"
#include <time.h> #include <time.h>
#include <ctype.h> #include <ctype.h>
#include <stdlib.h> #include <stdlib.h>
#include <errno.h> #include <errno.h>
#include "StreamFormatConverter.h"
#include "StreamError.h"
/* timezone in UNIX contains the seconds between UTC and local time, /* timezone in UNIX contains the seconds between UTC and local time,
but not in Free-BSD! Here timezone() is a function delivering but not in Free-BSD! Here timezone() is a function delivering
the time zone abbreviation (e.g. CET). Alternatively, the timezone the time zone abbreviation (e.g. CET). Alternatively, the timezone

View File

@ -171,10 +171,10 @@ static long writeData(dbCommon *record, format_t *format)
break; break;
#ifdef DBR_INT64 #ifdef DBR_INT64
case DBF_INT64: case DBF_INT64:
dval = ((epicsInt64 *)aai->bptr)[nowd]; dval = (double)((epicsInt64 *)aai->bptr)[nowd];
break; break;
case DBF_UINT64: case DBF_UINT64:
dval = ((epicsUInt64 *)aai->bptr)[nowd]; dval = (double)((epicsUInt64 *)aai->bptr)[nowd];
break; break;
#endif #endif
case DBF_LONG: case DBF_LONG:
@ -214,10 +214,10 @@ static long writeData(dbCommon *record, format_t *format)
{ {
#ifdef DBR_INT64 #ifdef DBR_INT64
case DBF_INT64: case DBF_INT64:
lval = ((epicsInt64 *)aai->bptr)[nowd]; lval = (long)((epicsInt64 *)aai->bptr)[nowd];
break; break;
case DBF_UINT64: case DBF_UINT64:
lval = ((epicsUInt64 *)aai->bptr)[nowd]; lval = (long)((epicsUInt64 *)aai->bptr)[nowd];
break; break;
#endif #endif
case DBF_LONG: case DBF_LONG:

View File

@ -20,7 +20,6 @@
* along with StreamDevice. If not, see https://www.gnu.org/licenses/. * along with StreamDevice. If not, see https://www.gnu.org/licenses/.
*************************************************************************/ *************************************************************************/
#include <stdio.h>
#include "epicsString.h" #include "epicsString.h"
#include "aaoRecord.h" #include "aaoRecord.h"
#include "devStream.h" #include "devStream.h"
@ -200,10 +199,10 @@ static long writeData(dbCommon *record, format_t *format)
break; break;
#ifdef DBR_INT64 #ifdef DBR_INT64
case DBF_INT64: case DBF_INT64:
dval = ((epicsInt64 *)aao->bptr)[nowd]; dval = (double)((epicsInt64 *)aao->bptr)[nowd];
break; break;
case DBF_UINT64: case DBF_UINT64:
dval = ((epicsUInt64 *)aao->bptr)[nowd]; dval = (double)((epicsUInt64 *)aao->bptr)[nowd];
break; break;
#endif #endif
case DBF_LONG: case DBF_LONG:
@ -243,10 +242,10 @@ static long writeData(dbCommon *record, format_t *format)
{ {
#ifdef DBR_INT64 #ifdef DBR_INT64
case DBF_INT64: case DBF_INT64:
lval = ((epicsInt64 *)aao->bptr)[nowd]; lval = (long)((epicsInt64 *)aao->bptr)[nowd];
break; break;
case DBF_UINT64: case DBF_UINT64:
lval = ((epicsUInt64 *)aao->bptr)[nowd]; lval = (long)((epicsUInt64 *)aao->bptr)[nowd];
break; break;
#endif #endif
case DBF_LONG: case DBF_LONG:

View File

@ -73,7 +73,7 @@ static long readData(dbCommon *record, format_t *format)
if (record->pact) return DO_NOT_CONVERT; if (record->pact) return DO_NOT_CONVERT;
/* In @init handler, no processing, enforce monitor updates. */ /* In @init handler, no processing, enforce monitor updates. */
ao->omod = ao->oval != val; ao->omod = ao->oval != val;
ao->orbv = ao->oval = val; ao->orbv = (epicsInt32)(ao->oval = val);
monitor_mask = recGblResetAlarms(record); monitor_mask = recGblResetAlarms(record);
if (!(fabs(ao->mlst - val) <= ao->mdel)) if (!(fabs(ao->mlst - val) <= ao->mdel))
{ {

View File

@ -37,7 +37,7 @@ static long readData(dbCommon *record, format_t *format)
{ {
lsi->val[length] = 0; lsi->val[length] = 0;
} }
lsi->len = length; lsi->len = (epicsUInt32)length;
return OK; return OK;
} }

View File

@ -39,7 +39,7 @@ static long readData(dbCommon *record, format_t *format)
{ {
lso->val[length] = 0; lso->val[length] = 0;
} }
lso->len = length; lso->len = (epicsUInt32)length;
if (record->pact) return OK; if (record->pact) return OK;
/* In @init handler, no processing, enforce monitor updates. */ /* In @init handler, no processing, enforce monitor updates. */
monitor_mask = recGblResetAlarms(record); monitor_mask = recGblResetAlarms(record);

View File

@ -43,7 +43,7 @@ static long readData(dbCommon *record, format_t *format)
strncmp(so->oval, so->val, sizeof(so->val))) strncmp(so->oval, so->val, sizeof(so->val)))
{ {
monitor_mask |= DBE_VALUE | DBE_LOG; monitor_mask |= DBE_VALUE | DBE_LOG;
strncpy(so->oval, so->val, sizeof(so->val)); strncpy(so->oval, so->val, sizeof(so->oval));
} }
if (monitor_mask) if (monitor_mask)
db_post_events(record, so->val, monitor_mask); db_post_events(record, so->val, monitor_mask);

View File

@ -172,10 +172,10 @@ static long writeData(dbCommon *record, format_t *format)
break; break;
#ifdef DBR_INT64 #ifdef DBR_INT64
case DBF_INT64: case DBF_INT64:
dval = ((epicsInt64 *)wf->bptr)[nowd]; dval = (double)((epicsInt64 *)wf->bptr)[nowd];
break; break;
case DBF_UINT64: case DBF_UINT64:
dval = ((epicsUInt64 *)wf->bptr)[nowd]; dval = (double)((epicsUInt64 *)wf->bptr)[nowd];
break; break;
#endif #endif
case DBF_LONG: case DBF_LONG:
@ -215,10 +215,10 @@ static long writeData(dbCommon *record, format_t *format)
{ {
#ifdef DBR_INT64 #ifdef DBR_INT64
case DBF_INT64: case DBF_INT64:
lval = ((epicsInt64 *)wf->bptr)[nowd]; lval = (long)((epicsInt64 *)wf->bptr)[nowd];
break; break;
case DBF_UINT64: case DBF_UINT64:
lval = ((epicsUInt64 *)wf->bptr)[nowd]; lval = (long)((epicsUInt64 *)wf->bptr)[nowd];
break; break;
#endif #endif
case DBF_LONG: case DBF_LONG:

View File

@ -32,6 +32,9 @@ if (@ARGV[0] eq "-3.13") {
} else { } else {
print "variable(streamDebug, int)\n"; print "variable(streamDebug, int)\n";
print "variable(streamError, int)\n"; print "variable(streamError, int)\n";
print "variable(streamDebugColored, int)\n";
print "variable(streamErrorDeadTime, int)\n";
print "variable(streamMsgTimeStamped, int)\n";
print "registrar(streamRegistrar)\n"; print "registrar(streamRegistrar)\n";
if ($asyn) { print "registrar(AsynDriverInterfaceRegistrar)\n"; } if ($asyn) { print "registrar(AsynDriverInterfaceRegistrar)\n"; }
} }

View File

@ -42,10 +42,12 @@ PROD_SRCS_vxWorks = -nil-
PROD_LIBS = stream PROD_LIBS = stream
ifdef ASYN ifdef ASYN
# edit asynRegistrars.dbd if necessary streamApp_DBD += asyn.dbd
streamApp_DBD += asynRegistrars.dbd streamApp_DBD += drvAsynIPPort.dbd
# add asynRecord.dbd if you like streamApp_DBD += drvAsynSerialPort.dbd
streamApp_DBD += asynRecord.dbd # vxi11 support is optional in recent asyn versions
#streamApp_DBD += drvVxi11.dbd
PROD_LIBS += asyn PROD_LIBS += asyn
# cygwin needs separate RPC library for asyn # cygwin needs separate RPC library for asyn
PROD_SYS_LIBS_cygwin32 += $(CYGWIN_RPC_LIB) PROD_SYS_LIBS_cygwin32 += $(CYGWIN_RPC_LIB)
@ -78,6 +80,13 @@ endif
PROD_LIBS += $(EPICS_BASE_IOC_LIBS) PROD_LIBS += $(EPICS_BASE_IOC_LIBS)
# Some linux systems moved RPC related symbols to libtirpc
# Define TIRPC in configure/CONFIG_SITE in this case
ifeq ($(TIRPC),YES)
USR_INCLUDES_Linux += -I/usr/include/tirpc
PROD_SYS_LIBS_DEFAULT += tirpc
endif
# switch off annoying rset warnings in 3.16+ # switch off annoying rset warnings in 3.16+
CPPFLAGS += -DUSE_TYPED_RSET CPPFLAGS += -DUSE_TYPED_RSET

View File

@ -1,9 +0,0 @@
registrar(asynRegister)
registrar(asynInterposeFlushRegister)
registrar(asynInterposeEosRegister)
# asynDriver up to version 4-16 does not support serial port for Windows!
registrar(drvAsynSerialPortRegisterCommands)
registrar(drvAsynIPPortRegisterCommands)
registrar(drvAsynIPServerPortRegisterCommands)
registrar(vxi11RegisterCommands)

View File

@ -54,4 +54,6 @@ write {
out "%s hexsum8 %0.12<hexsum8>"; out "%s hexsum8 %0.12<hexsum8>";
out "%s cpi %0.12<cpi>"; out "%s cpi %0.12<cpi>";
out "%s leybold %0.12<leybold>"; out "%s leybold %0.12<leybold>";
out "%s lrc %0.12<lrc>";
out "%s hexlrc %0.12<hexlrc>";
} }

View File

@ -19,7 +19,7 @@
# along with StreamDevice. If not, see https://www.gnu.org/licenses/. # along with StreamDevice. If not, see https://www.gnu.org/licenses/.
#########################################################################/ #########################################################################/
terminator = CR LF; terminator = LF;
cmd { cmd {
out "%s"; out "%s";

View File

@ -52,7 +52,11 @@ set protocol {
out "jamcrc %s %9.1<jamcrc>"; in "jamcrc %=s %9.1<jamcrc>"; out "jamcrc %s %9.1<jamcrc>"; in "jamcrc %=s %9.1<jamcrc>";
out "adler32 %s %9.1<adler32>"; in "adler32 %=s %9.1<adler32>"; out "adler32 %s %9.1<adler32>"; in "adler32 %=s %9.1<adler32>";
out "hexsum8 %s %9.1<hexsum8>"; in "hexsum8 %=s %9.1<hexsum8>"; out "hexsum8 %s %9.1<hexsum8>"; in "hexsum8 %=s %9.1<hexsum8>";
out "bitsum %s %9.1<bitsum>"; in "bitsum %=s %9.1<bitsum>";
out "bitsum8 %s %9.1<bitsum8>"; in "bitsum8 %=s %9.1<bitsum8>";
out "bitsum16 %s %9.1<bitsum16>"; in "bitsum16 %=s %9.1<bitsum16>";
out "bitsum32 %s %9.1<bitsum32>"; in "bitsum32 %=s %9.1<bitsum32>";
out "sum %s %09.1<sum>"; in "sum %=s %09.1<sum>"; out "sum %s %09.1<sum>"; in "sum %=s %09.1<sum>";
out "sum8 %s %09.1<sum8>"; in "sum8 %=s %09.1<sum8>"; out "sum8 %s %09.1<sum8>"; in "sum8 %=s %09.1<sum8>";
out "sum16 %s %09.1<sum16>"; in "sum16 %=s %09.1<sum16>"; out "sum16 %s %09.1<sum16>"; in "sum16 %=s %09.1<sum16>";
@ -88,6 +92,10 @@ set protocol {
out "jamcrc %s %09.1<jamcrc>"; in "jamcrc %=s %09.1<jamcrc>"; out "jamcrc %s %09.1<jamcrc>"; in "jamcrc %=s %09.1<jamcrc>";
out "adler32 %s %09.1<adler32>"; in "adler32 %=s %09.1<adler32>"; out "adler32 %s %09.1<adler32>"; in "adler32 %=s %09.1<adler32>";
out "hexsum8 %s %09.1<hexsum8>"; in "hexsum8 %=s %09.1<hexsum8>"; out "hexsum8 %s %09.1<hexsum8>"; in "hexsum8 %=s %09.1<hexsum8>";
out "bitsum %s %09.1<bitsum>"; in "bitsum %=s %09.1<bitsum>";
out "bitsum8 %s %09.1<bitsum8>"; in "bitsum8 %=s %09.1<bitsum8>";
out "bitsum16 %s %09.1<bitsum16>"; in "bitsum16 %=s %09.1<bitsum16>";
out "bitsum32 %s %09.1<bitsum32>"; in "bitsum32 %=s %09.1<bitsum32>";
out "sum %s %-9.1<sum>"; in "sum %=s %-9.1<sum>"; out "sum %s %-9.1<sum>"; in "sum %=s %-9.1<sum>";
out "sum8 %s %-9.1<sum8>"; in "sum8 %=s %-9.1<sum8>"; out "sum8 %s %-9.1<sum8>"; in "sum8 %=s %-9.1<sum8>";
@ -124,6 +132,10 @@ set protocol {
out "jamcrc %s %-9.1<jamcrc>"; in "jamcrc %=s %-9.1<jamcrc>"; out "jamcrc %s %-9.1<jamcrc>"; in "jamcrc %=s %-9.1<jamcrc>";
out "adler32 %s %-9.1<adler32>"; in "adler32 %=s %-9.1<adler32>"; out "adler32 %s %-9.1<adler32>"; in "adler32 %=s %-9.1<adler32>";
out "hexsum8 %s %-9.1<hexsum8>"; in "hexsum8 %=s %-9.1<hexsum8>"; out "hexsum8 %s %-9.1<hexsum8>"; in "hexsum8 %=s %-9.1<hexsum8>";
out "bitsum %s %-9.1<bitsum>"; in "bitsum %=s %-9.1<bitsum>";
out "bitsum8 %s %-9.1<bitsum8>"; in "bitsum8 %=s %-9.1<bitsum8>";
out "bitsum16 %s %-9.1<bitsum16>"; in "bitsum16 %=s %-9.1<bitsum16>";
out "bitsum32 %s %-9.1<bitsum32>"; in "bitsum32 %=s %-9.1<bitsum32>";
out "sum %s %#9.1<sum>"; in "sum %=s %#9.1<sum>"; out "sum %s %#9.1<sum>"; in "sum %=s %#9.1<sum>";
out "sum8 %s %#9.1<sum8>"; in "sum8 %=s %#9.1<sum8>"; out "sum8 %s %#9.1<sum8>"; in "sum8 %=s %#9.1<sum8>";
@ -160,7 +172,11 @@ set protocol {
out "jamcrc %s %#9.1<jamcrc>"; in "jamcrc %=s %#9.1<jamcrc>"; out "jamcrc %s %#9.1<jamcrc>"; in "jamcrc %=s %#9.1<jamcrc>";
out "adler32 %s %#9.1<adler32>"; in "adler32 %=s %#9.1<adler32>"; out "adler32 %s %#9.1<adler32>"; in "adler32 %=s %#9.1<adler32>";
out "hexsum8 %s %#9.1<hexsum8>"; in "hexsum8 %=s %#9.1<hexsum8>"; out "hexsum8 %s %#9.1<hexsum8>"; in "hexsum8 %=s %#9.1<hexsum8>";
out "bitsum %s %#9.1<bitsum>"; in "bitsum %=s %#9.1<bitsum>";
out "bitsum8 %s %#9.1<bitsum8>"; in "bitsum8 %=s %#9.1<bitsum8>";
out "bitsum16 %s %#9.1<bitsum16>"; in "bitsum16 %=s %#9.1<bitsum16>";
out "bitsum32 %s %#9.1<bitsum32>"; in "bitsum32 %=s %#9.1<bitsum32>";
out "sum %s %#09.1<sum>"; in "sum %=s %#09.1<sum>"; out "sum %s %#09.1<sum>"; in "sum %=s %#09.1<sum>";
out "sum8 %s %#09.1<sum8>"; in "sum8 %=s %#09.1<sum8>"; out "sum8 %s %#09.1<sum8>"; in "sum8 %=s %#09.1<sum8>";
out "sum16 %s %#09.1<sum16>"; in "sum16 %=s %#09.1<sum16>"; out "sum16 %s %#09.1<sum16>"; in "sum16 %=s %#09.1<sum16>";
@ -196,7 +212,11 @@ set protocol {
out "jamcrc %s %#09.1<jamcrc>"; in "jamcrc %=s %#09.1<jamcrc>"; out "jamcrc %s %#09.1<jamcrc>"; in "jamcrc %=s %#09.1<jamcrc>";
out "adler32 %s %#09.1<adler32>"; in "adler32 %=s %#09.1<adler32>"; out "adler32 %s %#09.1<adler32>"; in "adler32 %=s %#09.1<adler32>";
out "hexsum8 %s %#09.1<hexsum8>"; in "hexsum8 %=s %#09.1<hexsum8>"; out "hexsum8 %s %#09.1<hexsum8>"; in "hexsum8 %=s %#09.1<hexsum8>";
out "bitsum %s %#09.1<bitsum>"; in "bitsum %=s %#09.1<bitsum>";
out "bitsum8 %s %#09.1<bitsum8>"; in "bitsum8 %=s %#09.1<bitsum8>";
out "bitsum16 %s %#09.1<bitsum16>"; in "bitsum16 %=s %#09.1<bitsum16>";
out "bitsum32 %s %#09.1<bitsum32>"; in "bitsum32 %=s %#09.1<bitsum32>";
out "sum %s %#-9.1<sum>"; in "sum %=s %#-9.1<sum>"; out "sum %s %#-9.1<sum>"; in "sum %=s %#-9.1<sum>";
out "sum8 %s %#-9.1<sum8>"; in "sum8 %=s %#-9.1<sum8>"; out "sum8 %s %#-9.1<sum8>"; in "sum8 %=s %#-9.1<sum8>";
out "sum16 %s %#-9.1<sum16>"; in "sum16 %=s %#-9.1<sum16>"; out "sum16 %s %#-9.1<sum16>"; in "sum16 %=s %#-9.1<sum16>";
@ -232,6 +252,13 @@ set protocol {
out "jamcrc %s %#-9.1<jamcrc>"; in "jamcrc %=s %#-9.1<jamcrc>"; out "jamcrc %s %#-9.1<jamcrc>"; in "jamcrc %=s %#-9.1<jamcrc>";
out "adler32 %s %#-9.1<adler32>"; in "adler32 %=s %#-9.1<adler32>"; out "adler32 %s %#-9.1<adler32>"; in "adler32 %=s %#-9.1<adler32>";
out "hexsum8 %s %#-9.1<hexsum8>"; in "hexsum8 %=s %#-9.1<hexsum8>"; out "hexsum8 %s %#-9.1<hexsum8>"; in "hexsum8 %=s %#-9.1<hexsum8>";
out "bitsum %s %#-9.1<bitsum>"; in "bitsum %=s %#-9.1<bitsum>";
out "bitsum8 %s %#-9.1<bitsum8>"; in "bitsum8 %=s %#-9.1<bitsum8>";
out "bitsum16 %s %#-9.1<bitsum16>"; in "bitsum16 %=s %#-9.1<bitsum16>";
out "bitsum32 %s %#-9.1<bitsum32>"; in "bitsum32 %=s %#-9.1<bitsum32>";
# Check combination of regsub and checksum. Always check what the device sees.
out "crc8 %s%#/[0-9]{2}/&:/ %9.1<crc8>"; in "crc8 %#/[0-9]{2}/&:/%s %9.1<crc8>"; out "%s";
out "DONE"; out "DONE";
} }
} }
@ -313,7 +340,15 @@ assure "adler32 123456789 \x09\x1E\x01\xDE\n"
send "adler32 123456789 \x09\x1E\x01\xDE\n" send "adler32 123456789 \x09\x1E\x01\xDE\n"
assure "hexsum8 123456789 \x2D\n" assure "hexsum8 123456789 \x2D\n"
send "hexsum8 123456789 \x2D\n" send "hexsum8 123456789 \x2D\n"
assure "bitsum 123456789 \x21\n"
send "bitsum 123456789 \x21\n"
assure "bitsum8 123456789 \x21\n"
send "bitsum8 123456789 \x21\n"
assure "bitsum16 123456789 \x00\x21\n"
send "bitsum16 123456789 \x00\x21\n"
assure "bitsum32 123456789 \x00\x00\x00\x21\n"
send "bitsum32 123456789 \x00\x00\x00\x21\n"
assure "sum 123456789 DD\n" assure "sum 123456789 DD\n"
send "sum 123456789 DD\n" send "sum 123456789 DD\n"
assure "sum8 123456789 DD\n" assure "sum8 123456789 DD\n"
@ -384,7 +419,15 @@ assure "adler32 123456789 091E01DE\n"
send "adler32 123456789 091E01DE\n" send "adler32 123456789 091E01DE\n"
assure "hexsum8 123456789 2D\n" assure "hexsum8 123456789 2D\n"
send "hexsum8 123456789 2D\n" send "hexsum8 123456789 2D\n"
assure "bitsum 123456789 21\n"
send "bitsum 123456789 21\n"
assure "bitsum8 123456789 21\n"
send "bitsum8 123456789 21\n"
assure "bitsum16 123456789 0021\n"
send "bitsum16 123456789 0021\n"
assure "bitsum32 123456789 00000021\n"
send "bitsum32 123456789 00000021\n"
assure "sum 123456789 \x3D\x3D\n" assure "sum 123456789 \x3D\x3D\n"
send "sum 123456789 \x3D\x3D\n" send "sum 123456789 \x3D\x3D\n"
assure "sum8 123456789 \x3D\x3D\n" assure "sum8 123456789 \x3D\x3D\n"
@ -455,7 +498,15 @@ assure "adler32 123456789 \x30\x39\x31\x3E\x30\x31\x3D\x3E\n"
send "adler32 123456789 \x30\x39\x31\x3E\x30\x31\x3D\x3E\n" send "adler32 123456789 \x30\x39\x31\x3E\x30\x31\x3D\x3E\n"
assure "hexsum8 123456789 \x32\x3D\n" assure "hexsum8 123456789 \x32\x3D\n"
send "hexsum8 123456789 \x32\x3D\n" send "hexsum8 123456789 \x32\x3D\n"
assure "bitsum 123456789 \x32\x31\n"
send "bitsum 123456789 \x32\x31\n"
assure "bitsum8 123456789 \x32\x31\n"
send "bitsum8 123456789 \x32\x31\n"
assure "bitsum16 123456789 \x30\x30\x32\x31\n"
send "bitsum16 123456789 \x30\x30\x32\x31\n"
assure "bitsum32 123456789 \x30\x30\x30\x30\x30\x30\x32\x31\n"
send "bitsum32 123456789 \x30\x30\x30\x30\x30\x30\x32\x31\n"
assure "sum 123456789 \xDD\n" assure "sum 123456789 \xDD\n"
send "sum 123456789 \xDD\n" send "sum 123456789 \xDD\n"
assure "sum8 123456789 \xDD\n" assure "sum8 123456789 \xDD\n"
@ -526,7 +577,15 @@ assure "adler32 123456789 \xDE\x01\x1E\x09\n"
send "adler32 123456789 \xDE\x01\x1E\x09\n" send "adler32 123456789 \xDE\x01\x1E\x09\n"
assure "hexsum8 123456789 \x2D\n" assure "hexsum8 123456789 \x2D\n"
send "hexsum8 123456789 \x2D\n" send "hexsum8 123456789 \x2D\n"
assure "bitsum 123456789 \x21\n"
send "bitsum 123456789 \x21\n"
assure "bitsum8 123456789 \x21\n"
send "bitsum8 123456789 \x21\n"
assure "bitsum16 123456789 \x21\x00\n"
send "bitsum16 123456789 \x21\x00\n"
assure "bitsum32 123456789 \x21\x00\x00\x00\n"
send "bitsum32 123456789 \x21\x00\x00\x00\n"
assure "sum 123456789 DD\n" assure "sum 123456789 DD\n"
send "sum 123456789 DD\n" send "sum 123456789 DD\n"
assure "sum8 123456789 DD\n" assure "sum8 123456789 DD\n"
@ -597,7 +656,15 @@ assure "adler32 123456789 DE011E09\n"
send "adler32 123456789 DE011E09\n" send "adler32 123456789 DE011E09\n"
assure "hexsum8 123456789 2D\n" assure "hexsum8 123456789 2D\n"
send "hexsum8 123456789 2D\n" send "hexsum8 123456789 2D\n"
assure "bitsum 123456789 21\n"
send "bitsum 123456789 21\n"
assure "bitsum8 123456789 21\n"
send "bitsum8 123456789 21\n"
assure "bitsum16 123456789 2100\n"
send "bitsum16 123456789 2100\n"
assure "bitsum32 123456789 21000000\n"
send "bitsum32 123456789 21000000\n"
assure "sum 123456789 \x3D\x3D\n" assure "sum 123456789 \x3D\x3D\n"
send "sum 123456789 \x3D\x3D\n" send "sum 123456789 \x3D\x3D\n"
assure "sum8 123456789 \x3D\x3D\n" assure "sum8 123456789 \x3D\x3D\n"
@ -668,6 +735,20 @@ assure "adler32 123456789 \x3D\x3E\x30\x31\x31\x3E\x30\x39\n"
send "adler32 123456789 \x3D\x3E\x30\x31\x31\x3E\x30\x39\n" send "adler32 123456789 \x3D\x3E\x30\x31\x31\x3E\x30\x39\n"
assure "hexsum8 123456789 \x32\x3D\n" assure "hexsum8 123456789 \x32\x3D\n"
send "hexsum8 123456789 \x32\x3D\n" send "hexsum8 123456789 \x32\x3D\n"
assure "bitsum 123456789 \x32\x31\n"
send "bitsum 123456789 \x32\x31\n"
assure "bitsum8 123456789 \x32\x31\n"
send "bitsum8 123456789 \x32\x31\n"
assure "bitsum16 123456789 \x32\x31\x30\x30\n"
send "bitsum16 123456789 \x32\x31\x30\x30\n"
assure "bitsum32 123456789 \x32\x31\x30\x30\x30\x30\x30\x30\n"
send "bitsum32 123456789 \x32\x31\x30\x30\x30\x30\x30\x30\n"
# check regsub and checksums
assure "crc8 12:34:56:78:9 \x07\n" ;# modified output string and checksum
send "crc8 123456789 \xF4\n" ;# original input string and checksum
assure "12:34:56:78:9\n" ;# modified input string
assure "DONE\n" assure "DONE\n"
finish finish

View File

@ -1,6 +1,7 @@
rm -f test.* rm -f test.*
cat > test.cc << EOF cat > test.cc << EOF
#include <StreamError.h>
#include <StreamBuffer.h> #include <StreamBuffer.h>
#include <assert.h> #include <assert.h>
#include <stdio.h> #include <stdio.h>
@ -55,14 +56,14 @@ EOF
if [ "$1" = "-sls" ] if [ "$1" = "-sls" ]
then then
O=../../O.*_$EPICS_HOST_ARCH/StreamBuffer.o O=../../O.*_$EPICS_HOST_ARCH
else else
O=../../src/O.$EPICS_HOST_ARCH/StreamBuffer.o O=../../src/O.$EPICS_HOST_ARCH
fi fi
for o in $O for o in $O
do do
g++ -I ../../src $o test.cc -o test.exe g++ -I ../../src $o/StreamBuffer.o $o/StreamError.o test.cc -o test.exe
./test.exe ./test.exe
if [ $? != 0 ] if [ $? != 0 ]
then then