Compare commits

...

19 Commits

Author SHA1 Message Date
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
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
19 changed files with 477 additions and 183 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

View File

@ -24,11 +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/StreamProtocol.h
HEADERS += src/StreamBusInterface.h HEADERS += src/StreamBusInterface.h
HEADERS += src/StreamCore.h HEADERS += src/StreamCore.h
HEADERS += src/MacroMagic.h HEADERS += src/MacroMagic.h
HEADERS += $(COMMON_DIR)/StreamVersion.h
CPPFLAGS += -DSTREAM_INTERNAL -I$(COMMON_DIR) CPPFLAGS += -DSTREAM_INTERNAL -I$(COMMON_DIR)

View File

@ -4,10 +4,6 @@ TOP=..
include $(TOP)/configure/CONFIG 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

@ -594,6 +594,18 @@ In input, the next byte or bytes must match the checksum.
href="https://en.wikipedia.org/wiki/Longitudinal_redundancy_check">Wikipedia</a>.</dd> href="https://en.wikipedia.org/wiki/Longitudinal_redundancy_check">Wikipedia</a>.</dd>
<dt><code>%&lt;hexlrc&gt;</code></dt> <dt><code>%&lt;hexlrc&gt;</code></dt>
<dd>One byte. The LRC for the hex digits. (Other characters are ignored.)</dd> <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>

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

@ -385,35 +385,45 @@ 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>
<p> <p>
By default the debug/error output is set to be colored if the terminal allows By default, error messages to the console are printed in red color if
it but this can be set to always colored or never colored by setting <var>stderr</var> is a tty at startup time, using ANSI color codes. Some
<code>streamDebugColored</code> to 1 or 0 respectively. 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>
<p> <p>
Error and debug messages are prefixed with a time stamp unless the variable Error and debug messages are prefixed with a time stamp unless the variable
<code>streamMsgTimeStamped</code> is set to 0. <code>streamMsgTimeStamped</code> is set to 0.
</p> </p>
<p> <p>
When a device is disconnected StreamDevice can produce many repeated timeout when a device is unresponsive, StreamDevice may produce many repeated timeout
messages. To reduce this logging you can set <code>streamErrorDeadTime</code> messages. To reduce this, you can set <code>streamErrorDeadTime</code>
to an integer number of seconds. When this is set repeated timeout messages to an integer number of seconds. In this case, repeated timeout messages
will not be printed in the specified dead time after the last message. The will not be printed during the specified dead time after the last printed
default dead time is 0, resulting in every message being printed. message. The default dead time is 0, resulting in every message being printed.
</p> </p>
<h3>Example (vxWorks):</h3> <h3>Example (vxWorks):</h3>

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,7 +152,7 @@ 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 {
@ -150,9 +160,10 @@ a[target=ex]:hover:after {
} }
@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

@ -20,43 +20,57 @@
* along with StreamDevice. If not, see https://www.gnu.org/licenses/. * along with StreamDevice. If not, see https://www.gnu.org/licenses/.
*************************************************************************/ *************************************************************************/
#if defined(vxWorks) #ifdef vxWorks
#include <version.h> #include <version.h>
#if defined(_WRS_VXWORKS_MAJOR) && _WRS_VXWORKS_MAJOR > 6 || (_WRS_VXWORKS_MAJOR == 6 && _WRS_VXWORKS_MINOR > 8) /* VxWorks has strncasecmp since version 6
#include <stdint.h> but availability depends on configuration.
#define PRIX32 "X" We cannot know.
#define PRIu32 "u" */
#define NEED_strncasecmp
/* VxWorks does not have inttypes.h and uint32_t differs between versions */
#if defined(_WRS_VXWORKS_MAJOR) && (_WRS_VXWORKS_MAJOR > 6 || (_WRS_VXWORKS_MAJOR == 6 && _WRS_VXWORKS_MINOR >= 9))
#define PRIX32 "X"
#define PRIu32 "u"
#else
#define PRIX32 "lX"
#define PRIu32 "lu"
#endif
#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 PRIX32 "lX" #define __STDC_FORMAT_MACROS
#define PRIu32 "lu" #include <inttypes.h>
#endif
#define PRIX8 "X"
#define uint_fast8_t uint8_t
#define int_fast8_t int8_t
#elif defined(_MSC_VER) && _MSC_VER < 1700 /* Visual Studio 2010 does not have inttypes.h */
#include <stdint.h>
#define PRIX32 "X"
#define PRIu32 "u"
#define PRIX8 "X"
#else
#define __STDC_FORMAT_MACROS
#include <stdint.h>
#include <inttypes.h>
#endif #endif
#include <ctype.h> #include <ctype.h>
#include <stdlib.h>
#if defined(__rtems__)
#include <rtems.h>
#if __RTEMS_MAJOR__ < 5
/* RTEMS has strncasecmp since version 5 */
#define NEED_strncasecmp
#endif
#endif
#ifdef _MSC_VER #ifdef _MSC_VER
#define strncasecmp _strnicmp /* Windows strncasecmp has a different name. */
#define strncasecmp _strnicmp
#endif #endif
#if defined(vxWorks) || defined(__rtems__) #ifdef NEED_strncasecmp
// These systems have no strncasecmp at the moment // Have no strncasecmp but avoid compiler errors in case it exists in future versions
// But avoid compiler errors in case strncasecmp exists in future versions extern "C" {
static int mystrncasecmp(const char *s1, const char *s2, size_t n) static int mystrncasecmp(const char *s1, const char *s2, size_t n)
{ {
int r=0; int r=0;
while (n && (r = toupper(*s1)-toupper(*s2)) == 0) { n--; s1++; s2++; }; while (n && (r = toupper(*s1)-toupper(*s2)) == 0) { n--; s1++; s2++; };
return r; return r;
}
} }
#define strncasecmp mystrncasecmp #define strncasecmp mystrncasecmp
#endif #endif
@ -534,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;
} }
@ -731,7 +746,7 @@ 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(); size_t length = output.length();
@ -752,8 +767,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
{ {
@ -812,7 +827,7 @@ 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; size_t length = cursor;
if (length >= start) length -= start; if (length >= start) length -= start;
else length = 0; else length = 0;
@ -824,7 +839,7 @@ scanPseudo(const StreamFormat& format, StreamBuffer& input, size_t& cursor)
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 :
@ -867,7 +882,7 @@ 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
@ -910,8 +925,8 @@ 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

View File

@ -102,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 --------------->
@ -254,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;
} }
} }
@ -358,7 +390,7 @@ 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(ansiEscape(ANSI_BG_WHITE)); 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++)
{ {

View File

@ -603,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);
@ -1011,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());

View File

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

View File

@ -25,6 +25,7 @@
#include <stdarg.h> #include <stdarg.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdio.h>
#if defined(vxWorks) #if defined(vxWorks)
#include <symLib.h> #include <symLib.h>
@ -136,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,
@ -933,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",
@ -1027,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

@ -171,8 +171,6 @@ print(const char* fmt, ...)
{ {
va_list args; va_list args;
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;
if (streamMsgTimeStamped) if (streamMsgTimeStamped)
{ {
@ -184,7 +182,13 @@ print(const char* fmt, ...)
{ {
fprintf(fp, "%s ", StreamGetThreadNameFunction()); fprintf(fp, "%s ", StreamGetThreadNameFunction());
} }
fprintf(fp, "%s:%d: ", f, line); 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);

View File

@ -56,19 +56,15 @@ 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:StreamDebugObject(__FILE__,__LINE__).print #define debug2 (streamDebug<2)?0:StreamDebugClass(__FILE__,__LINE__).print
/* /*
* ANSI escape sequences for terminal output * ANSI escape sequences for terminal output

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