Compare commits
175 Commits
stream_2_3
...
stream_2_5
Author | SHA1 | Date | |
---|---|---|---|
e5dfe7b816 | |||
6efb17be64 | |||
0310b30446 | |||
863cdf3d7f | |||
e51bb2555e | |||
9e0157d679 | |||
3e91eb4fc1 | |||
caf811c23a | |||
de116c9e5a | |||
eef38b0557 | |||
0057899476 | |||
0148e6e27e | |||
c6af859f9d | |||
01f0761ca8 | |||
d7770846aa | |||
0e76ba6464 | |||
fcb44ec845 | |||
cec8195e5e | |||
6c0eb32d75 | |||
9ae5f1d583 | |||
3b60bb9420 | |||
db50c35ad6 | |||
4f05d4e254 | |||
c5a17b217d | |||
fdccb0c5a9 | |||
ccbafef55a | |||
c594134317 | |||
11cfaf44ee | |||
a34678cbcc | |||
26fd99823c | |||
42c305c79b | |||
9585506644 | |||
4317ae0f75 | |||
6e6c79f5ea | |||
8da0014091 | |||
60d292b3ea | |||
5ce9b737cf | |||
7bcb494044 | |||
13758eb08e | |||
b9c26cb6f8 | |||
e3dd0a319a | |||
80b7ceea2d | |||
eabcce156a | |||
615beb7d02 | |||
78e6c9355c | |||
20129ea341 | |||
388f950559 | |||
96c93714a4 | |||
252eb44959 | |||
0442e2ae4f | |||
31dae22428 | |||
1d5c7adb61 | |||
c138442a95 | |||
a672ab1234 | |||
c550cc01be | |||
7db71589d8 | |||
910cf77d97 | |||
807fe825d6 | |||
ff46c9a4a7 | |||
a42fbc9c6c | |||
74d950ab2d | |||
9049baf730 | |||
050a165a82 | |||
1428b85a34 | |||
e4f8a6ed40 | |||
5a5b42cb67 | |||
e62553eca6 | |||
0846c1dd5b | |||
774e88241a | |||
03a04a51b6 | |||
103e74be03 | |||
129cc61759 | |||
70073e1b69 | |||
c94c1a1e70 | |||
07026b03ae | |||
5a6d293a5f | |||
d51d6648e0 | |||
f521da8606 | |||
b64d486925 | |||
7f1f1c1e65 | |||
2dc0918926 | |||
37d412108a | |||
f03af416eb | |||
82066af0e0 | |||
9c18afb178 | |||
04c6cb50d7 | |||
382784526c | |||
a91496884c | |||
7cdec03dcb | |||
3e5e4b8375 | |||
f8b944f325 | |||
c7e7878d9e | |||
5717dd6345 | |||
2fe3b5c68d | |||
db88081fd7 | |||
1230c01646 | |||
8142dd27eb | |||
19279b208a | |||
9619c359c1 | |||
6b948abc2d | |||
2c7391602a | |||
fd0fccda46 | |||
1164413bd2 | |||
7e1e038ecd | |||
2a8b0aed56 | |||
c856dec6dd | |||
53bb235ff1 | |||
29ac760f0e | |||
7836af6423 | |||
1af9c6ea55 | |||
8c619d3350 | |||
0155d5ab69 | |||
8252b629f5 | |||
494661c0e7 | |||
5b652abd25 | |||
a3e68d7f7d | |||
7cc19d2614 | |||
318d0e389b | |||
8dbe3abcdb | |||
649d775cd3 | |||
a941061a29 | |||
100b7d4543 | |||
1eb8a25432 | |||
9b6f8b3386 | |||
cc1bb004f2 | |||
ab6a6b730d | |||
3d928c49a0 | |||
8fe70f2e39 | |||
862686aeef | |||
2fa54cdfb1 | |||
d987a9abb9 | |||
d37c142d36 | |||
35768cbfbb | |||
5494aae1ba | |||
18ba904d6c | |||
2cd969d643 | |||
0c9d4dc4c0 | |||
ef3b2ec9fd | |||
1fb402e838 | |||
eae987e7f1 | |||
93906dd022 | |||
7e717c564d | |||
14b60c210a | |||
8dae0afa30 | |||
95bf2c78c2 | |||
8c10e5395c | |||
69de0b9f31 | |||
22ae510dca | |||
5fdf3912c8 | |||
b50aec424f | |||
4a1033b6dc | |||
68e178d41b | |||
e83ceda41c | |||
b307adb005 | |||
0a2a304a2b | |||
80477709aa | |||
706c59d069 | |||
6541c40818 | |||
04f64c0f67 | |||
af669103c2 | |||
40d9641150 | |||
9cf2770440 | |||
616bc9a410 | |||
53abbb750b | |||
ec3cbbf559 | |||
8a3cbc1c6a | |||
8a4cda6647 | |||
2d513b96d3 | |||
6a0d7b4492 | |||
755df7c3ee | |||
ddb602196a | |||
0a5f486a9b | |||
574052dcb8 | |||
eaba256c51 | |||
7eab96120f |
5
MODULE
Normal file
@ -0,0 +1,5 @@
|
||||
# Please change the following email with yours.
|
||||
Email: dirk.zimoch@psi.ch
|
||||
Module-Name: StreamDevice2
|
||||
Description: StreamDevice2
|
||||
Project-Name:
|
68
Makefile
@ -1,54 +1,24 @@
|
||||
include /ioc/tools/driver.makefile
|
||||
EXCLUDE_VERSIONS = 3.13.2
|
||||
PROJECT=stream2
|
||||
BUILDCLASSES += Linux
|
||||
TOP = ..
|
||||
|
||||
DOCUDIR = doc
|
||||
DIRS = src
|
||||
streamApp_DEPEND_DIRS = src
|
||||
|
||||
DBDS = stream.dbd
|
||||
|
||||
BUSSES += AsynDriver
|
||||
FORMATS += Enum
|
||||
FORMATS += BCD
|
||||
FORMATS += Raw
|
||||
FORMATS += Binary
|
||||
FORMATS += Checksum
|
||||
FORMATS += Regexp
|
||||
FORMATS += Exponential
|
||||
RECORDTYPES += aai aao
|
||||
RECORDTYPES += ao ai
|
||||
RECORDTYPES += bo bi
|
||||
RECORDTYPES += mbbo mbbi
|
||||
RECORDTYPES += mbboDirect mbbiDirect
|
||||
RECORDTYPES += longout longin
|
||||
RECORDTYPES += stringout stringin
|
||||
RECORDTYPES += waveform
|
||||
RECORDTYPES += calcout
|
||||
|
||||
SOURCES += $(RECORDTYPES:%=src/dev%Stream.c)
|
||||
SOURCES += $(FORMATS:%=src/%Converter.cc)
|
||||
SOURCES += $(BUSSES:%=src/%Interface.cc)
|
||||
SOURCES += $(wildcard src/Stream*.cc)
|
||||
SOURCES += src/StreamVersion.c
|
||||
|
||||
ifeq (${EPICS_BASETYPE},3.13)
|
||||
USR_INCLUDES += -include $(INSTALL_INCLUDE)/compat3_13.h
|
||||
# Look if we have EPICS R3.13 or R3.14
|
||||
ifeq ($(wildcard $(TOP)/configure),)
|
||||
# EPICS R3.13
|
||||
include $(TOP)/config/CONFIG_APP
|
||||
CONFIG = $(TOP)/config
|
||||
else
|
||||
# EPICS R3.14
|
||||
include $(TOP)/configure/CONFIG
|
||||
ifneq ($(words $(CALC) $(SYNAPPS)), 0)
|
||||
# with synApps calc module (contains scalcout)
|
||||
DIRS += srcSynApps
|
||||
srcSynApps_DEPEND_DIRS = src
|
||||
streamApp_DEPEND_DIRS += srcSynApps
|
||||
endif
|
||||
endif
|
||||
|
||||
StreamCore.o: streamReferences
|
||||
DIRS += streamApp
|
||||
|
||||
streamReferences:
|
||||
@for i in $(BUSSES); \
|
||||
do echo "extern void* ref_$${i}Interface;"; \
|
||||
echo "void* p$$i = ref_$${i}Interface;"; \
|
||||
done > $@
|
||||
@for i in $(FORMATS); \
|
||||
do echo "extern void* ref_$${i}Converter;"; \
|
||||
echo "void* p$$i = ref_$${i}Converter;"; \
|
||||
done >> $@
|
||||
|
||||
stream.dbd:
|
||||
@for r in $(RECORDTYPES); \
|
||||
do echo "device($$r,INST_IO,dev$${r}Stream,\"stream\")"; \
|
||||
done > $@
|
||||
@echo "driver(stream)" >> $@
|
||||
include $(CONFIG)/RULES_DIRS
|
||||
|
BIN
doc/EPICS.gif
Before Width: | Height: | Size: 453 B |
BIN
doc/SLS.gif
Before Width: | Height: | Size: 2.2 KiB |
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: aai Records</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: aai Records</h1>
|
||||
<p>
|
||||
<b>Note:</b> aai record support is disabled per default.
|
||||
@ -123,6 +124,5 @@ the written or read value.
|
||||
<a href="scalcout.html">scalcout</a>
|
||||
</p>
|
||||
<p><small>Dirk Zimoch, 2006</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: aao Records</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: aao Records</h1>
|
||||
<p>
|
||||
<b>Note:</b> aao record support is disabled per default.
|
||||
@ -123,6 +124,5 @@ the written or read value.
|
||||
<a href="scalcout.html">scalcout</a>
|
||||
</p>
|
||||
<p><small>Dirk Zimoch, 2006</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: ai Records</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: ai Records</h1>
|
||||
|
||||
<h2>Normal Operation</h2>
|
||||
@ -38,6 +39,8 @@ written or read value.
|
||||
definition. <em>StreamDevice</em> does not set it. For example,
|
||||
<code>EOFF=-10</code> and <code>ESLO=0.000305180437934</code>
|
||||
(=20.0/0xFFFF) maps 0x0000 to -10.0, 0x7FFF to 0.0 and 0xFFFF to 10.0.
|
||||
Using unsigned formats with values ≥ 0x800000 gives different results
|
||||
on 64 bit machines.
|
||||
</dd>
|
||||
<dt>ENUM format (e.g. <code>%{</code>):</dt>
|
||||
<dd>
|
||||
@ -76,6 +79,5 @@ written or read value.
|
||||
<a href="scalcout.html">scalcout</a>
|
||||
</p>
|
||||
<p><small>Dirk Zimoch, 2005</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: ao Records</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: ao Records</h1>
|
||||
|
||||
<h2>Normal Operation</h2>
|
||||
@ -37,6 +38,8 @@ written or read value.
|
||||
might be set in the record definition. <em>StreamDevice</em> does not set it.
|
||||
For example, <code>EOFF=-10</code> and <code>ESLO=0.000305180437934</code>
|
||||
(=20.0/0xFFFF) maps -10.0 to 0x0000, 0.0 to 0x7FFF and 10.0 to 0xFFFF.
|
||||
Using unsigned formats with values ≥ 0x800000 gives different results
|
||||
on 64 bit machines.
|
||||
</dd>
|
||||
<dt>ENUM format (e.g. <code>%{</code>):</dt>
|
||||
<dd>
|
||||
@ -77,6 +80,5 @@ written or read value.
|
||||
<a href="scalcout.html">scalcout</a>
|
||||
</p>
|
||||
<p><small>Dirk Zimoch, 2005</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
BIN
doc/bg.gif
Before Width: | Height: | Size: 868 B |
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: bi Records</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: bi Records</h1>
|
||||
|
||||
<h2>Normal Operation</h2>
|
||||
@ -72,6 +73,5 @@ written or read value.
|
||||
<a href="scalcout.html">scalcout</a>
|
||||
</p>
|
||||
<p><small>Dirk Zimoch, 2005</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: bo Records</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: bo Records</h1>
|
||||
|
||||
<h2>Normal Operation</h2>
|
||||
@ -71,6 +72,5 @@ written or read value.
|
||||
<a href="scalcout.html">scalcout</a>
|
||||
</p>
|
||||
<p><small>Dirk Zimoch, 2005</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: Bus API</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>Bus API</h1>
|
||||
|
||||
<a name="class"></a>
|
||||
@ -640,6 +641,5 @@ other request method like <code>writeRequest()</code>.
|
||||
</p>
|
||||
<hr>
|
||||
<p><small>Dirk Zimoch, 2007</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: calcout Records</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: calcout Records</h1>
|
||||
|
||||
<p>
|
||||
@ -78,6 +79,5 @@ Different record fields are used for output and input. The variable
|
||||
<a href="scalcout.html">scalcout</a>
|
||||
</p>
|
||||
<p><small>Dirk Zimoch, 2005</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: Using EPICS 3.13</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: Using EPICS 3.13</h1>
|
||||
|
||||
<a name="pre"></a>
|
||||
@ -219,6 +220,5 @@ asynSetOption ("PS1", 0, "stop", "1")
|
||||
|
||||
<hr>
|
||||
<p><small>Dirk Zimoch, 2006</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
BIN
doc/favicon.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
@ -3,18 +3,219 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: Format Converter API</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: Format Converter API</h1>
|
||||
|
||||
<h2>Sorry, this documentation is still missing.</h2>
|
||||
<a name="class"></a>
|
||||
<h2>Converter Class</h2>
|
||||
<p>
|
||||
A user defined converter class inherits public from
|
||||
<i>StreamFormatConverter</i> and handles one or more conversion characters.
|
||||
It is not necessary that a given conversion character supports both,
|
||||
printing and scanning.
|
||||
But if it does, both must be handled by the same converter class.
|
||||
</p>
|
||||
<p>
|
||||
Any conversion corresponds to one data type. The converter class must
|
||||
implement print and/or scan methods for this data type. It must also
|
||||
implement a parse method to analyse the format string.
|
||||
</p>
|
||||
<p>
|
||||
A converter class must be registered with a call to RegisterConverter()
|
||||
in the global file context.
|
||||
</p>
|
||||
<p>
|
||||
The converter must not contain any class variables, because there will be
|
||||
only one global instance for each conversion character - not one for each
|
||||
format string!
|
||||
</p>
|
||||
|
||||
<h3>Example: LONG converter for <code>%Q</code></h3>
|
||||
<pre>
|
||||
#include "StreamFormatConverter.h"
|
||||
|
||||
class MyConverter : public StreamFormatConverter
|
||||
{
|
||||
int parse(const StreamFormat&, StreamBuffer&, const char*&, bool);
|
||||
bool printLong(const StreamFormat&, StreamBuffer&, long);
|
||||
int scanLong(const StreamFormat&, const char*, long&);
|
||||
};
|
||||
|
||||
RegisterConverter(MyConverter,"Q");
|
||||
|
||||
// ... (implementation)
|
||||
</pre>
|
||||
|
||||
<a name="theory"></a>
|
||||
<h2>Theroy of Operation</h2>
|
||||
|
||||
<a name="registration"></a>
|
||||
<h3>Registration </h3>
|
||||
<div class="indent"><code>
|
||||
RegisterConverter(<i>converterClass</i>, "<i>characters</i>");
|
||||
</code></div>
|
||||
<p>
|
||||
This macro registers the converter class for all given conversion
|
||||
characters. In most cases, you will give only one character.
|
||||
The macro must be called once for each class in the global file context.
|
||||
</p>
|
||||
<p>
|
||||
HINT: Do not branch depending on the conversion character.
|
||||
Provide multiple classes, that's more efficient.
|
||||
</p>
|
||||
|
||||
<a name="parsing"></a>
|
||||
<h3>Parsing</h3>
|
||||
<div class="indent"><code>
|
||||
int parse (const StreamFormat& fmt, StreamBuffer& info,
|
||||
const char*& source, bool scanFormat);
|
||||
</code></div>
|
||||
<div class="indent"><code>
|
||||
struct StreamFormat {
|
||||
char conv;
|
||||
StreamFormatType type;
|
||||
unsigned char flags;
|
||||
short prec;
|
||||
unsigned short width;
|
||||
unsigned short infolen;
|
||||
const char* info;
|
||||
};
|
||||
</code></div>
|
||||
|
||||
<p>
|
||||
During initialization, <code>parse()</code> is called whenever one of the
|
||||
conversion characters handled by your converter class is found in a protocol.
|
||||
The fields <code>fmt.conv</code>, <code>fmt.flags</code>,
|
||||
<code>fmt.prec</code>, and <code>fmt.width</code> have
|
||||
already been filled in. If a scan format is parsed, <code>scanFormat</code>
|
||||
is <code>true</code>. If a print format is parsed, <code>scanFormat</code>
|
||||
is <code>false</code>.
|
||||
</p>
|
||||
<p>
|
||||
The <code>fmt.flags</code> field is a bitset and can have any of the following
|
||||
flags set:
|
||||
</p>
|
||||
<ul>
|
||||
<li><code>left_flag</code>: the format contained a <code>-</code>.
|
||||
This is normaly used to indicate that the value should be printed
|
||||
left-aligned.
|
||||
</li>
|
||||
<li><code>sign_flag</code>: the format contained a <code>+</code>.
|
||||
This normaly requests to print a sign even for positive numbers.
|
||||
</li>
|
||||
<li><code>space_flag</code>: the format contained a '<code> </code>' (space).
|
||||
This normaly requests to print a space instead of a sign for positive numbers.
|
||||
</li>
|
||||
<li><code>alt_flag</code>: the format contained a <code>#</code>.
|
||||
This indicated the request to use an alternative format. For example in
|
||||
<code>%#x</code> the hex number is preficed with <code>0x</code>.
|
||||
</li>
|
||||
<li><code>zero_flag</code>: the format contained a <code>0</code>.
|
||||
This normaly requests to pad a numerical value with leading zeros
|
||||
instead of leading spaces.
|
||||
<li><code>skip_flag</code>: the format contained a <code>*</code>.
|
||||
The value is parsed and checked but then discarded.
|
||||
</ul>
|
||||
<p>
|
||||
It is not necessary that these flags have exactly the same meaning in your
|
||||
formats, but a similar and intuitive meaning helpful for the user.
|
||||
</p>
|
||||
<p>
|
||||
There are two additional flags, <code>default_flag</code> indicating a
|
||||
<code>?</code> and <code>compare_flag</code> indicating a <code>=</code>
|
||||
int the format, that are handled internally by <em>StreamDevice</em> and
|
||||
are not of interest to the converter class.
|
||||
</p>
|
||||
<p>
|
||||
The <code>source</code> pointer points to the character of the format string
|
||||
just after the conversion character. You can parse additional characters if
|
||||
they belong to the format string handled by your class.
|
||||
Move the <code>source</code> pointer so that is points to the first character
|
||||
after your format string.
|
||||
This is done for example in the builtin formats
|
||||
<code>%[charset]</code> or <code>%{enum0|enum1}</code>.
|
||||
However, many formats don't need additional characters.
|
||||
|
||||
</p>
|
||||
<h4>Example</h4>
|
||||
<pre>
|
||||
source source
|
||||
before after
|
||||
parse() parse()
|
||||
| |
|
||||
"%39[0-9a-zA-Z]constant text"
|
||||
|
|
||||
conversion
|
||||
character
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
You can write any data you may need later in <code>print*()</code> or
|
||||
<code>scan*()</code> to the
|
||||
<a href="streambuffer.html"><i>Streambuffer</i></a> <code>info</code>.
|
||||
This will probably be necessary if you have parsed additional characters
|
||||
from the format string as in the above example<br>
|
||||
</p>
|
||||
<p>
|
||||
Return <code>long_format</code>, <em>double_format</em>,
|
||||
<em>string_format</em>, or <code>enum_format</code> depending on the
|
||||
datatype associated with the conversion character.
|
||||
It is not necessary to return the same value for print and for scan
|
||||
formats.
|
||||
You can even return different values depending on the format string,
|
||||
but I can't imagine why anyone should do that.
|
||||
</p>
|
||||
<p>
|
||||
If the format is not a real data conversion but does other things with
|
||||
the data (append or check a checksum, encode or decode the data,...),
|
||||
return <code>pseudo_format</code>.
|
||||
</p>
|
||||
<p>
|
||||
Return <code>false</code> if there is any parse error or if print or scan
|
||||
is requested but not supported by this conversion.
|
||||
</p>
|
||||
|
||||
<a name="printing_scanning"></a>
|
||||
<h3>Printing and Scanning</h3>
|
||||
<p>
|
||||
Provide a <code>print[Long|Double|String|Pseudo]()</code> and/or
|
||||
<code>scan[Long|Double|String|Pseudo]()</code> method appropriate
|
||||
for the data type you have returned in the <code>parse()</code> method.
|
||||
That method is called whenever the conversion appears in an output or input,
|
||||
respectively.
|
||||
You only need to implement the flavour of print and/or scan suitable for
|
||||
the datatype returned by <code>parse()</code>.
|
||||
</p>
|
||||
<p>
|
||||
Now, <code>fmt.type</code> contains the value returned by <code>parse()</code>.
|
||||
With <code>fmt.info()</code> get access to the string you have written to
|
||||
<code>info</code> in <code>parse()</code> (null terminated).
|
||||
</p>
|
||||
<p>
|
||||
The length of the info string can be found in <code>fmt.infolen</code>.
|
||||
</p>
|
||||
<p>
|
||||
In <code>print*()</code>, append the converted value to <code>output</code>.
|
||||
Do not modify what is already in output (unless you really know what you're
|
||||
doing).
|
||||
Return <code>true</code> on success, <code>false</code> on failure.
|
||||
</p>
|
||||
<p>
|
||||
In <code>scan*()</code>, read the value from input and return the number of
|
||||
consumed bytes.
|
||||
In the string version, don't write more bytes than <code>maxlen</code>!
|
||||
If the <code>skip_flag</code> is set, you don't need to write to
|
||||
<code>value</code>, since the value will be discarded anyway.
|
||||
Return <code>-1</code> on failure.
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
<p><small>Dirk Zimoch, 2006</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
<p><small>Dirk Zimoch, 2007</small></p>
|
||||
</body>
|
||||
</html>
|
||||
|
237
doc/formats.html
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: Format Converters</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: Format Converters</h1>
|
||||
|
||||
<a name="syntax"></a>
|
||||
@ -27,8 +28,8 @@ A format converter consists of
|
||||
</p>
|
||||
<ul>
|
||||
<li>The <code>%</code> character</li>
|
||||
<li>Optionally a field name in <code>()</code></li>
|
||||
<li>Optionally flags out of the characters <code>*# +0-</code></li>
|
||||
<li>Optionally a field <span class="new">or record</span> name in <code>()</code></li>
|
||||
<li>Optionally flags out of the characters <code>*# +0-<span class="new">?=</span></code></li>
|
||||
<li>Optionally an integer <em>width</em> field</li>
|
||||
<li>Optionally a period character (<code>.</code>) followed
|
||||
by an integer <em>precision</em> field (input ony for most formats)</li>
|
||||
@ -36,6 +37,11 @@ A format converter consists of
|
||||
<li>Additional information required by some converters</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
The flags <code>*# +0-</code> work like in the C functions
|
||||
<em>printf()</em> and <em>scanf()</em>.
|
||||
The flags <code>?</code> and <code>=</code> are extensions.
|
||||
</p>
|
||||
<p>
|
||||
The <code>*</code> flag skips data in input formats.
|
||||
Input is consumed and parsed, a mismatch is an error, but the read
|
||||
@ -61,6 +67,16 @@ The <code>0</code> flag says that numbers should be left padded with
|
||||
The <code>-</code> flag specifies that output is left justified if
|
||||
<em>width</em> is larger than required.
|
||||
</p>
|
||||
<p class="new">
|
||||
The <code>?</code> flag makes failing input conversions succeed with
|
||||
a default zero value (0, 0.0, or "", depending on the format type).
|
||||
</p>
|
||||
<p class="new">
|
||||
The <code>=</code> flag allows to compare input with current values.
|
||||
It is only allowed in input formats.
|
||||
Instead of reading a new value from input, the current value is
|
||||
formatted (like for output) and then compared to the input.
|
||||
</p>
|
||||
|
||||
<h3>Examples:</h3>
|
||||
<table>
|
||||
@ -84,6 +100,14 @@ The <code>-</code> flag specifies that output is left justified if
|
||||
<td><code>in "%*i";</code></td>
|
||||
<td>skipped integer number</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>in "%?d";</code></td>
|
||||
<td>decimal number or nothing (read as 0)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>in "%=.3f";</code></td>
|
||||
<td>compare input to the current value formatted as a float with precision 3</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<a name="types"></a>
|
||||
@ -108,18 +132,25 @@ Thus, data type modifiers like <code>l</code> or <code>h</code> do not
|
||||
exist in <em>StreamDevice</em> formats.
|
||||
</p>
|
||||
|
||||
<h3>Accessing record fields directly</h3>
|
||||
<a name="redirection"></a>
|
||||
<h3>Redirection</h3>
|
||||
<p>
|
||||
To use other fields of the record or even fields of other records on the
|
||||
same IOC for the conversion, write the field name in parentheses directly
|
||||
after the <code>%</code>.
|
||||
For example <code>out "%(EGU)s";</code> outputs the <code>EGU</code>
|
||||
field formatted as a string.
|
||||
Use <code>in "%(<i>otherrecord</i>.VAL)f";</code> to write the floating
|
||||
point input value into the <code>VAL</code> field of
|
||||
<code><i>otherrecord</i></code>.
|
||||
(You can't skip <code>.VAL</code> here.)
|
||||
This is very useful when one line of input contains many values that should
|
||||
Use <code>in "%(<i>otherrecord</i>.RVAL)f";</code> to write the floating
|
||||
point input value into the <code>RVAL</code> field of
|
||||
<code><i>otherrecord</i></code>.
|
||||
<span class="new">
|
||||
If no field is given for an other record .VAL is assumed.
|
||||
When a record name conflicts with a field name use .VAL explicitly.
|
||||
</span>
|
||||
|
||||
</p>
|
||||
<p>
|
||||
This feature is very useful when one line of input contains many values that should
|
||||
be distributed to many records.
|
||||
If <code><i>otherrecord</i></code> is passive and the field has the PP
|
||||
attribute (see
|
||||
@ -129,6 +160,8 @@ It is your responsibility that the data type of the record field is
|
||||
compatible to the the data type of the converter.
|
||||
Note that using this syntax is by far not as efficient as using the
|
||||
default field.
|
||||
At the moment it is not possible to set <code>otherrecord</code> to an alarm
|
||||
state when anything fails.
|
||||
</p>
|
||||
|
||||
<h3>Pseudo-converters</h3>
|
||||
@ -144,31 +177,42 @@ No data type corresponds to those <em>pseudo-converters</em> and the
|
||||
<h2>3. Standard DOUBLE Converters (<code>%f</code>, <code>%e</code>,
|
||||
<code>%E</code>, <code>%g</code>, <code>%G</code>)</h2>
|
||||
<p>
|
||||
In output, <code>%f</code> prints fixed point, <code>%e</code> prints
|
||||
<b>Output:</b> <code>%f</code> prints fixed point, <code>%e</code> prints
|
||||
exponential notation and <code>%g</code> prints either fixed point or
|
||||
exponential depending on the magnitude of the value.
|
||||
<code>%E</code> and <code>%G</code> use <code>E</code> instead of
|
||||
<code>e</code> to separate the exponent.
|
||||
</p>
|
||||
<p>
|
||||
With the <code>#</code> flag, output always contains a period character.
|
||||
</p>
|
||||
<p>
|
||||
In input, all these formats are equivalent.
|
||||
Leading whitespaces are skipped.
|
||||
<b>Input:</b> All these formats are equivalent. Leading whitespaces are skipped.
|
||||
</p>
|
||||
<p class="new">
|
||||
With the <code>#</code> flag additional whitespace between sign and number
|
||||
is accepted.
|
||||
</p>
|
||||
<p class="new">
|
||||
When a maximum field width is given, leading whitespace only counts to the
|
||||
field witdth when the space flag is given.
|
||||
</p>
|
||||
|
||||
<a name="stdl"></a>
|
||||
<h2>4. Standard LONG Converters (<code>%d</code>, <code>%i</code>,
|
||||
<code>%u</code>, <code>%o</code>, <code>%x</code>, <code>%X</code>)</h2>
|
||||
<p>
|
||||
In output, <code>%d</code> and <code>%i</code> print signed decimal,
|
||||
<b>Output</b>: <code>%d</code> and <code>%i</code> print signed decimal,
|
||||
<code>%u</code> unsigned decimal, <code>%o</code> unsigned octal, and
|
||||
<code>%x</code> or <code>%X</code> unsigned hexadecimal.
|
||||
<code>%X</code> uses upper case letters.
|
||||
</p>
|
||||
<p>
|
||||
With the <code>#</code> flag, octal values are prefixed with <code>0</code>
|
||||
and hexadecimal values with <code>0x</code> or <code>0X</code>.
|
||||
</p>
|
||||
<p>
|
||||
In input, <code>%d</code> matches signed decimal, <code>%u</code> matches
|
||||
<b>Input:</b> <code>%d</code> matches signed decimal, <code>%u</code> matches
|
||||
unsigned decimal, <code>%o</code> unsigned octal.
|
||||
<code>%x</code> and <code>%X</code> both match upper or lower case unsigned
|
||||
hexadecimal.
|
||||
@ -177,23 +221,40 @@ Octal and hexadecimal values can optionally be prefixed.
|
||||
hexadecimal notation.
|
||||
Leading whitespaces are skipped.
|
||||
</p>
|
||||
<p class="new">
|
||||
With the <code>-</code> negative octal and hexadecimal values are accepted.
|
||||
</p>
|
||||
<p class="new">
|
||||
With the <code>#</code> flag additional whitespace between sign and number
|
||||
is accepted.
|
||||
</p>
|
||||
<p class="new">
|
||||
When a maximum field width is given, leading whitespace only counts to the
|
||||
field witdth when the space flag is given.
|
||||
</p>
|
||||
|
||||
<a name="stds"></a>
|
||||
<h2>5. Standard STRING Converters (<code>%s</code>, <code>%c</code>)</h2>
|
||||
<p>
|
||||
In output, <code>%s</code> prints a string.
|
||||
<b>Output:</b> <code>%s</code> prints a string.
|
||||
If <em>precision</em> is specified, this is the maximum string length.
|
||||
<code>%c</code> is a LONG format in output, printing one character!
|
||||
</p>
|
||||
<p>
|
||||
In input, <code>%s</code> matches a sequence of non-whitespace characters
|
||||
and <code>%c</code> a sequence of not-null characters.
|
||||
<b>Input:</b> <code>%s</code> matches a sequence of non-whitespace characters
|
||||
and <code>%c</code> matches a sequence of not-null characters.
|
||||
The maximum string length is given by <em>width</em>.
|
||||
The default <em>width</em> is infinite for <code>%s</code> and
|
||||
1 for <code>%c</code>.
|
||||
Leading whitespaces are skipped with <code>%s</code> but not with
|
||||
<code>%c</code>.
|
||||
The empty string matches.
|
||||
Leading whitespaces are skipped with <code>%s</code>
|
||||
<span class="new">
|
||||
except when the space flag is given</span>
|
||||
but not with <code>%c</code>.
|
||||
The empty string matches.
|
||||
</p>
|
||||
<p class="new">
|
||||
With the <code>#</code> flag <code>%s</code> matches a sequence of not-null
|
||||
characters instead of non-whitespace characters.
|
||||
</p>
|
||||
|
||||
<a name="cset"></a>
|
||||
@ -217,14 +278,24 @@ to <code>z</code>.
|
||||
This format maps an unsigned integer value on a set of strings.
|
||||
The value 0 corresponds to <em>string0</em> and so on.
|
||||
The strings are separated by <code>|</code>.
|
||||
If one of the strings contains <code>|</code> or <code>}</code>,
|
||||
a <code>\</code> must be used to escape the character.
|
||||
</p>
|
||||
<p>
|
||||
Example: <code>%{OFF|STANDBY|ON}</code> mapps the string <code>OFF</code>
|
||||
to the value 0, <code>STANDBY</code> to 1 and <code>ON</code> to 2.
|
||||
</p>
|
||||
<p>
|
||||
When using the <code>#</code> flag it is allowed to assign integer values
|
||||
to the strings using <code>=</code>.
|
||||
</p>
|
||||
<p>
|
||||
Example: <code>%#{neg=-1|stop=0|pos=1|fast=10}</code>.
|
||||
</p>
|
||||
<p>
|
||||
If one of the strings contains <code>|</code> or <code>}</code>
|
||||
(or <code>=</code> if the <code>#</code> flag is used)
|
||||
a <code>\</code> must be used to escape the character.
|
||||
</p>
|
||||
<p>
|
||||
In output, depending on the value, one of the strings is printed.
|
||||
</p>
|
||||
<p>
|
||||
@ -240,18 +311,19 @@ The <code>%b</code> format uses the characters <code>0</code> and
|
||||
<code>1</code>.
|
||||
With the <code>%B</code> format, you can choose two other characters
|
||||
to represent zero and one.
|
||||
With the <code>#</code> flag, the bit order is changed to <em>little
|
||||
endian</em>, i.e. least significant bit first.
|
||||
</p>
|
||||
<p>
|
||||
Examples: <code>%B.!</code> or <code>%B\x00\xff</code>.
|
||||
<code>%B01</code> is equivalent to <code>%b</code>.
|
||||
</p>
|
||||
<p>
|
||||
If in output <em>width</em> is larger than the number of significant bits,
|
||||
In output, if <em>width</em> is larger than the number of significant bits,
|
||||
then the flag <code>0</code> means that the value should be padded with
|
||||
with the chosen zero character instead of spaces.
|
||||
If <em>precision</em> is set, it means the number of significant bits.
|
||||
Otherwise, the highest 1 bit defines the number of significant bits.
|
||||
There is no alternate format when <code>#</code> is used.
|
||||
</p>
|
||||
<p>
|
||||
In input, leading spaces are skipped.
|
||||
@ -264,27 +336,46 @@ one character.
|
||||
<h2>9. Raw LONG Converter (<code>%r</code>)</h2>
|
||||
<p>
|
||||
The raw converter does not really "convert".
|
||||
A signed integer value is written or read in the internal
|
||||
A signed or unsigned integer value is written or read in the internal
|
||||
(usually two's complement) representation of the computer.
|
||||
The normal byte order is <em>big endian</em>, i.e. most significant byte
|
||||
first.
|
||||
With the <code>#</code> flag, the byte order is changed to <em>little
|
||||
endian</em>, i.e. least significant byte first.
|
||||
With the <code>0</code> flag, the value is unsigned, otherwise signed.
|
||||
</p>
|
||||
<p>
|
||||
In output, the <em>width</em> least significant bytes of the value
|
||||
are written.
|
||||
If <em>width</em> is larger than the size of a <code>long</code>,
|
||||
the value is sign extended.
|
||||
In output, the <em>prec</em> (or sizeof(long) whatever is less) least
|
||||
significant bytes of the value are sign extended or zero extended
|
||||
(depending on the <code>0</code> flag) to <em>width</em> bytes.
|
||||
</p>
|
||||
<p>
|
||||
In input, <em>width</em> bytes are read and put into the value.
|
||||
If <em>width</em> is longer than the size of a <code>long</code>, only
|
||||
If <em>width</em> is larger than the size of a <code>long</code>, only
|
||||
the least significant bytes are used.
|
||||
If <em>width</em> is smaller than the size of a <code>long</code>,
|
||||
the value is sign extended or zero extended, depending on the
|
||||
<code>0</code> flag.
|
||||
</p>
|
||||
<p>
|
||||
Example: <code>out "%.2r"</code>
|
||||
</p>
|
||||
|
||||
<a name="rawdouble"></a>
|
||||
<h2>10. Raw DOUBLE Converter (<code>%R</code>)</h2>
|
||||
<p>
|
||||
The raw converter does not really "convert".
|
||||
A float or double value is written or read in the internal
|
||||
(maybe IEEE) representation of the computer.
|
||||
The normal byte order is <em>big endian</em>, i.e. most significant byte
|
||||
first.
|
||||
With the <code>#</code> flag, the byte order is changed to <em>little
|
||||
endian</em>, i.e. least significant byte first.
|
||||
The <em>width</em> must be 4 (float) or 8 (double). The default is 4.
|
||||
</p>
|
||||
|
||||
<a name="bcd"></a>
|
||||
<h2>10. Packed BCD (Binary Coded Decimal) LONG Converter (<code>%D</code>)</h2>
|
||||
<h2>11. Packed BCD (Binary Coded Decimal) LONG Converter (<code>%D</code>)</h2>
|
||||
<p>
|
||||
Packed BCD is a format where each byte contains two binary coded
|
||||
decimal digits (<code>0</code> ... <code>9</code>).
|
||||
@ -313,7 +404,7 @@ than 9.
|
||||
</p>
|
||||
|
||||
<a name="chksum"></a>
|
||||
<h2>11. Checksum Pseudo-Converter (<code>%<<em>checksum</em>></code>)</h2>
|
||||
<h2>12. Checksum Pseudo-Converter (<code>%<<em>checksum</em>></code>)</h2>
|
||||
<p>
|
||||
This is not a normal "converter", because no user data is converted.
|
||||
Instead, a checksum is calculated from the input or output.
|
||||
@ -392,6 +483,9 @@ In input, the next byte or bytes must match the checksum.
|
||||
href="http://www.joegeluso.com/software/articles/ccitt.htm">correct?</a>)
|
||||
implementation of the CCITT standard 16 bit crc checksum with augment.
|
||||
(poly=0x1021, init=0x1D0F, xorout=0x0000).</dd>
|
||||
<dt><code>%<ccitt16x></code> or <code>%<crc16c></code> or <code>%<xmodem></code></dt>
|
||||
<dd>Two bytes. The XMODEM checksum.
|
||||
(poly=0x1021, init=0x0000, xorout=0x0000).</dd>
|
||||
<dt><code>%<crc32></code></dt>
|
||||
<dd>Four bytes. The standard 32 bit crc checksum.
|
||||
(poly=0x04C11DB7, init=0xFFFFFFFF, xorout=0xFFFFFFFF).</dd>
|
||||
@ -443,7 +537,7 @@ If <em>prec</em> is given, it specifies the sub-expression whose match
|
||||
is retuned.
|
||||
Otherwise the complete match is returned.
|
||||
In any case, the complete match is consumed from the input buffer.
|
||||
If the expression contains a <code>/</code> is must be escaped.
|
||||
If the expression contains a <code>/</code> it must be escaped.
|
||||
</p>
|
||||
<p>
|
||||
Example: <code>%.1/<title>(.*)<\/title>/</code> returns
|
||||
@ -451,9 +545,82 @@ the title of an HTML page, skipps anything before the
|
||||
<code><title></code> tag and leaves anything after the
|
||||
<code></title></code> tag in the input buffer.
|
||||
</p>
|
||||
<a name="mantexp"></a>
|
||||
<h2>13. MantissaExponent DOUBLE converter (<code>%m</code>)</h2>
|
||||
<p>
|
||||
This exotic and experimental format matches numbers in the format
|
||||
<i>[sign] mantissa sign exponent</i>, e.g <code>+123-4</code> meaning
|
||||
123e-4 or 0.0123. Mantissa and exponent are decimal integers.
|
||||
The sign of the mantissa is optional.
|
||||
Compared to the standard <code>%e</code> format, this format does not
|
||||
contain the characters <code>.</code> and <code>e</code>.
|
||||
</p>
|
||||
<p>
|
||||
Output formatting is ambigous (e.g. <code>123-4</code> versus
|
||||
<code>1230-5</code>). I chose the following convention:
|
||||
Format <em>precision</em> defines number of digits in mantissa.
|
||||
No leading '0' in mantissa (except for 0.0 of course).
|
||||
Number of digits in exponent is at least 2.
|
||||
Format flags <code>+</code>, <code>-</code>, and space are supported in
|
||||
the usual way (always sign, left justified, space instead of + sign).
|
||||
Flags <code>#</code> and <code>0</code> are unsupported.
|
||||
</p>
|
||||
<div class="new">
|
||||
<a name="timestamp"></a>
|
||||
<h2>14. Timestamp DOUBLE converter (<code>%T(<em>timeformat</em>)</code>)</h2>
|
||||
<p>
|
||||
This format reads or writes timestamps and converts them to a double number.
|
||||
The value represents the number of seconds since 1970 (the UNIX epoch).
|
||||
The precision of a double is large enough for microseconds (but not for
|
||||
nanoseconds). This format is probably used best in combination with a
|
||||
redirection to the <code>TIME</code> field. In this case, the value is
|
||||
converted to EPICS timestamps (seconds since 1990 and nanoseconds).
|
||||
The timestamp format understands the usual converters that the C function
|
||||
<em>strftime()</em> understands. In addition, fractions of a second can
|
||||
be specified and the time zone can be set in the format string.
|
||||
</p>
|
||||
<p>
|
||||
Example: <code>%(TIME)T(%d %b %Y %H:%M:%.3S %z)</code> may print something like
|
||||
<code> 3 Sep 2010 15:45:59 +0200</code>.
|
||||
</p>
|
||||
<p>
|
||||
Fractions of a second can be specified as <code>%.<em>n</em>S</code>
|
||||
(seconds with <em>n</em> fractional digits), as <code>%0<em>n</em>f</code>
|
||||
or <code>%<em>n</em>f</code> (<em>n</em> fractional digits) or as
|
||||
<code>%N</code> (nanoseconds).
|
||||
In input, <em>n</em> is the maximum number of digits parsed, there may be
|
||||
actually less digits in the input.
|
||||
If <em>n</em> is not specified (<code>%.S</code> or <code>%f</code>) it uses
|
||||
a default value of 6.
|
||||
</p>
|
||||
<p>
|
||||
In input, the time zone can be specified in the format like
|
||||
<code>%+<em>hhmm</em></code> or <code>%-<em>hhmm</em></code> for cases
|
||||
where the parsed time stamp does not specify the time zone, where
|
||||
<em>hhmm</em> is a 4 digit number specifying the offset in hours and minutes.
|
||||
</p>
|
||||
<p>
|
||||
In output, the system function <em>strftime()</em> is used to format the time.
|
||||
There may be differences in the implementation between operating systems.
|
||||
</p>
|
||||
<p>
|
||||
In input, <em>StreamDevice</em> used its own implementation because many
|
||||
systems are missing the <em>strptime()</em> function and additional formats
|
||||
are supported.
|
||||
</p>
|
||||
<p>
|
||||
Day of the week can be parsed but is ignored because the information is
|
||||
redundant when used together with day, month and year and more or less
|
||||
useless otherwise. No check is done for consistency.
|
||||
</p>
|
||||
<p>
|
||||
Because of the complexity of the problem, locales are not supported.
|
||||
Thus, only the English month names can be used (week day names are
|
||||
ignored anyway).
|
||||
</p>
|
||||
</div>
|
||||
<hr>
|
||||
<p align="right"><a href="processing.html">Next: Record Processing</a></p>
|
||||
<p><small>Dirk Zimoch, 2007</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
<p><small>Dirk Zimoch, 2011</small></p>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,32 +0,0 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Headbar</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
<style type="text/css">
|
||||
<!--
|
||||
body {margin:0px 0px 0px 0px;}
|
||||
big {font-size: 30px; font-family:serif;}
|
||||
-->
|
||||
</style>
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
<table cellspacing=0 cellpadding=0 border=0 width="100%">
|
||||
<tr><td bgcolor="#267ab8" colspan=6 valign="bottom" height=9 width="100%"></td></tr>
|
||||
<tr><td bgcolor="#000000" colspan=6 valign="bottom" height=1 width="100%"></td></tr>
|
||||
<tr>
|
||||
<td><img src="space.gif" width=180 height=1 alt=""><center><a href="http://www.psi.ch" target="ex"><img src="PSI.gif" width=150 height=55 alt="PSI" border=0></a></center></td>
|
||||
<td><img src="space.gif" width=1 height=65 alt=""></td>
|
||||
<td><a href="http://www.aps.anl.gov/epics" target="ex"><img
|
||||
src="EPICS.gif" border=0 width=50 height=50 alt="EPICS"></a></td>
|
||||
<td width="100%" align="center" nowrap>
|
||||
<big><b><em id="subtitle">StreamDevice</em></b></big></td>
|
||||
<td><img src="space.gif" width=166 height=1 alt=""><center><a href="http://sls.web.psi.ch/view.php/about/index.html" target="ex"><img src="SLS.gif" width=146 height=55 alt="SLS" border=0></a></center></td>
|
||||
</tr>
|
||||
<tr><td bgcolor="#267ab8" colspan=6 valign="bottom" height=4 width="100%"></td></tr>
|
||||
<tr><td bgcolor="#000000" colspan=6 valign="bottom" height=1 width="100%"></td></tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
124
doc/index.html
@ -1,20 +1,116 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
|
||||
"http://www.w3.org/TR/html4/frameset.dtd">
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice Documentation</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<title>StreamDevice</title>
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<frameset rows="80,*" framespacing=0 frameborder=0 border=0>
|
||||
<frame src="head.html" name="head" scrolling="no" noresize>
|
||||
<frameset cols="180,*" >
|
||||
<frame src="nav.html" name="nav" noresize>
|
||||
<frame src="stream.html" name="text">
|
||||
</frameset>
|
||||
<noframes>
|
||||
<a href="stream.html">Click here for no-frames version.</a>
|
||||
</noframes>
|
||||
</frameset>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>EPICS <em>StreamDevice</em></h1>
|
||||
|
||||
<h2>What is <em>StreamDevice</em>?</h2>
|
||||
<p>
|
||||
<em>StreamDevice</em> is a generic
|
||||
<a href="http://www.aps.anl.gov/epics" target="ex">EPICS</a>
|
||||
device support for devices with a "byte stream" based
|
||||
communication interface.
|
||||
That means devices that can be controlled by sending and
|
||||
receiving strings (in the broadest sense, including non-printable
|
||||
characters and even null-bytes).
|
||||
Examples for this type of communication interface are
|
||||
serial line (RS-232, RS-485, ...),
|
||||
IEEE-488 (also known as GPIB or HP-IB), and telnet-like TCP/IP.
|
||||
</p>
|
||||
<p>
|
||||
<em>StreamDevice</em> is not limited to a specific device type or
|
||||
manufacturer nor is it necessary to re-compile anything to support a
|
||||
new device type.
|
||||
Instead, it can be configured for any device type with
|
||||
<a href="protocol.html"><em>protocol files</em></a> in plain ASCII text
|
||||
which describes the commands a device understands and the replies it sends.
|
||||
If the device can be controlled with strings like
|
||||
"<kbd>RF:FREQ 499.655 MHZ</kbd>" <em>StreamDevice</em> can be used.
|
||||
Formatting and parsing of values is done with
|
||||
<a href="formats.html"><em>format converters</em></a> similar to those
|
||||
known from the C functions <em>printf()</em> and <em>scanf()</em>.
|
||||
To support other formats, it is possible to
|
||||
<a href="formatconverter.html">write your own converters</a>.
|
||||
</p>
|
||||
<p>
|
||||
Each record with <em>StreamDevice</em> support runs one protocol
|
||||
from the protocol file to read or write its value.
|
||||
Protocols can be as simple as just one output string or can
|
||||
consist of many strings sent to and read from the device.
|
||||
However, a protocol is linear.
|
||||
That means it runs from start to end each time the record is
|
||||
<a href="processing.html">processed</a>.
|
||||
It does not provide loops or branches.
|
||||
</p>
|
||||
<p>
|
||||
<em>StreamDevice</em> comes with an interface to<a target="ex"
|
||||
href="http://www.aps.anl.gov/epics/modules/soft/asyn/">
|
||||
<em>asynDriver</em></a>
|
||||
but can be extended to
|
||||
<a href="businterface.html">support other bus drivers</a>.
|
||||
Note that <em>StreamDevice</em> is not an alternative or replacement
|
||||
but a supplement for <em>asynDriver</em>.
|
||||
<em>StreamDevice</em> converts record values to and from strings but
|
||||
leaves it to <em>asynDriver</em> (or other bus interfaces) to exchange
|
||||
these strings with the device.
|
||||
Thus any bus type supported by <em>asynDriver</em> (to be exact by
|
||||
<em>asynOctet</em>) can automatically be used with <em>StreamDevice</em>.
|
||||
</p>
|
||||
<p>
|
||||
<em>StreamDevice</em> supports all
|
||||
<a href="recordtypes.html">standard records</a> of EPICS base
|
||||
which can have device support.
|
||||
It is also possible to
|
||||
<a href="recordinterface.html">write support for new record types</a>.
|
||||
</p>
|
||||
|
||||
<h2>What is <em>StreamDevice</em> not?</h2>
|
||||
<p>
|
||||
It is not a programming language for a high-level application.
|
||||
It is, for example, not possible to write a complete scanning program
|
||||
in a protocol.
|
||||
Use other tools for that and use <em>StreamDevice</em> only for the
|
||||
primitive commands.
|
||||
</p>
|
||||
<p>
|
||||
It is not a block oriented device support.
|
||||
It is not possible to send or receive huge blocks of data that contain
|
||||
many process variables distributed over many records.
|
||||
</p>
|
||||
|
||||
<h2>Recommended Readings</h2>
|
||||
<p>
|
||||
<a href="http://www.aps.anl.gov/epics/base/R3-14/8-docs/AppDevGuide.pdf"
|
||||
target="ex">IOC Application Developer's Guide (PDF)</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="http://www.aps.anl.gov/asd/controls/epics/EpicsDocumentation/AppDevManuals/RecordRef/Recordref-1.html"
|
||||
target="ex">EPICS Record Reference Manual</a>
|
||||
</p>
|
||||
|
||||
<h2>Color and Style Conventions</h2>
|
||||
<p>
|
||||
In this document, code is witten in <code>green fixed width font</code>.
|
||||
This marks text you typically type in configuration files etc.
|
||||
</p>
|
||||
<pre>
|
||||
Longer code segments are often set in a box.
|
||||
</pre>
|
||||
<p>
|
||||
Important modifications and new features are
|
||||
<span class="new">highlighted with a yellow background</span>.
|
||||
Obsolete features are <strike class="new">highlighted and crossed out</strike>.
|
||||
</p>
|
||||
<hr>
|
||||
<p align="right"><a href="setup.html">Next: Setup</a></p>
|
||||
<p><small>Dirk Zimoch, 2011</small></p>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: longin Records</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: longin Records</h1>
|
||||
|
||||
<h2>Normal Operation</h2>
|
||||
@ -25,6 +26,8 @@ written or read value.
|
||||
<dd>
|
||||
<u>Output:</u> <code><i>x</i>=VAL</code><br>
|
||||
<u>Input:</u> <code>VAL=<i>x</i></code>
|
||||
Using unsigned formats with values ≥ 0x800000 gives different results
|
||||
on 64 bit machines.
|
||||
</dd>
|
||||
<dt>ENUM format (e.g. <code>%{</code>):</dt>
|
||||
<dd>
|
||||
@ -63,6 +66,5 @@ written or read value.
|
||||
<a href="scalcout.html">scalcout</a>
|
||||
</p>
|
||||
<p><small>Dirk Zimoch, 2005</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: longout Records</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: longout Records</h1>
|
||||
|
||||
<h2>Normal Operation</h2>
|
||||
@ -25,6 +26,8 @@ written or read value.
|
||||
<dd>
|
||||
<u>Output:</u> <code><i>x</i>=VAL</code><br>
|
||||
<u>Input:</u> <code>VAL=<i>x</i></code>
|
||||
Using unsigned formats with values ≥ 0x800000 gives different results
|
||||
on 64 bit machines.
|
||||
</dd>
|
||||
<dt>ENUM format (e.g. <code>%{</code>):</dt>
|
||||
<dd>
|
||||
@ -63,6 +66,5 @@ written or read value.
|
||||
<a href="scalcout.html">scalcout</a>
|
||||
</p>
|
||||
<p><small>Dirk Zimoch, 2005</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: mbbi Records</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: mbbi Records</h1>
|
||||
|
||||
<h2>Normal Operation</h2>
|
||||
@ -89,6 +90,5 @@ written or read value.
|
||||
<a href="scalcout.html">scalcout</a>
|
||||
</p>
|
||||
<p><small>Dirk Zimoch, 2005</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: mbbiDirect Records</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: mbbiDirect Records</h1>
|
||||
|
||||
<h2>Normal Operation</h2>
|
||||
@ -75,6 +76,5 @@ written or read value.
|
||||
<a href="scalcout.html">calcout</a>
|
||||
</p>
|
||||
<p><small>Dirk Zimoch, 2005</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: mbbo Records</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: mbbo Records</h1>
|
||||
|
||||
<h2>Normal Operation</h2>
|
||||
@ -90,6 +91,5 @@ written or read value.
|
||||
<a href="scalcout.html">scalcout</a>
|
||||
</p>
|
||||
<p><small>Dirk Zimoch, 2005</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: mbboDirect Records</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: mbboDirect Records</h1>
|
||||
|
||||
<h2>Normal Operation</h2>
|
||||
@ -77,6 +78,5 @@ written or read value.
|
||||
<a href="scalcout.html">scalcout</a>
|
||||
</p>
|
||||
<p><small>Dirk Zimoch, 2005</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
375
doc/nav.html
@ -5,215 +5,210 @@
|
||||
<title>Navbar</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
<script type="text/javascript">
|
||||
<!--
|
||||
active="";
|
||||
function show(name) {
|
||||
var m = document.getElementById(name);
|
||||
var b = document.getElementById(name+"Button");
|
||||
if (active != "") { hide(active); }
|
||||
m.style.display = "block";
|
||||
b.innerHTML="[«]";
|
||||
b.href="javascript:hide(\'"+name+"\')"
|
||||
active=name;
|
||||
}
|
||||
function hide(name) {
|
||||
var m = document.getElementById(name);
|
||||
var b = document.getElementById(name+"Button");
|
||||
m.style.display = "none";
|
||||
b.innerHTML="[»]";
|
||||
b.href="javascript:show(\'"+name+"\')"
|
||||
active="";
|
||||
}
|
||||
function printview() {
|
||||
w = window.open(parent.text.location, "_parent", "dependent=yes,width=800,menubar=yes,toolbar=yes,location=yes");
|
||||
}
|
||||
-->
|
||||
</script>
|
||||
<style type="text/css">
|
||||
<!--
|
||||
body {margin:0px 0px 0px 0px;
|
||||
font-family:sans-serif;
|
||||
body {margin:0 0 13ex 1em;
|
||||
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
|
||||
font-weight:bold;
|
||||
font-size:13px;}
|
||||
font-size:13px;
|
||||
background-color:#e3eaf6;}
|
||||
a {text-decoration:none;}
|
||||
a:link {color:#333333;}
|
||||
a:visited {color:#333333;}
|
||||
a:hover {color:#FF0000;}
|
||||
a:active {color:#FF0000;}
|
||||
.top {margin-bottom: 0px;
|
||||
margin-top: 10px;
|
||||
border-bottom: solid 1px black;}
|
||||
.top ul {margin-left: 5px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
font-size:95%;
|
||||
line-height:120%;
|
||||
padding-left:1em;}
|
||||
.subsub {margin-left: 15px;
|
||||
line-height:120%;
|
||||
font-size:80%;}
|
||||
|
||||
h1 {font-size:120%;
|
||||
margin-top: 20px;
|
||||
margin-right: 20px;
|
||||
margin-bottom: 10px;
|
||||
color:#267ab8;}
|
||||
|
||||
div {border-bottom:1px solid black;
|
||||
padding-top:1ex;}
|
||||
|
||||
div div, div div div, div:hover div div
|
||||
{
|
||||
padding-top:0;
|
||||
margin-right:.1em;
|
||||
border:none;
|
||||
box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
padding 0;
|
||||
display:block;
|
||||
position:relative;
|
||||
}
|
||||
div div
|
||||
{display:none;
|
||||
margin-left:2em;
|
||||
margin-right:1em;
|
||||
background-color:#e3eaf6;
|
||||
padding:0 .5em .5ex .5em;
|
||||
border-width:0 thick thick 1px;
|
||||
border-color:black;
|
||||
border-style:solid;
|
||||
}
|
||||
:root *> div div
|
||||
{border-width:0 1px 1px 1px;
|
||||
box-shadow: 5px 5px 5px black;
|
||||
-moz-box-shadow: 5px 5px 5px black;
|
||||
-webkit-box-shadow: 5px 5px 5px black;
|
||||
}
|
||||
div:hover div {
|
||||
position:absolute;
|
||||
display:block;}
|
||||
|
||||
div div div {margin-left:0;}
|
||||
|
||||
a {display:block;
|
||||
margin-top:.3ex;}
|
||||
|
||||
div div a {display:list-item;
|
||||
margin-left:1em;
|
||||
list-style-type:disc;
|
||||
}
|
||||
|
||||
div div div a {list-style-type:circle;}
|
||||
|
||||
-->
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body background="bg.gif">
|
||||
<p align="center">
|
||||
</p>
|
||||
<div style="margin-left: 20px;">
|
||||
<h1>User's Guide</h1>
|
||||
<div class="top">
|
||||
<a target="text" href="stream.html">Intro</a>
|
||||
</div>
|
||||
<div class="top">
|
||||
<a target="text" href="setup.html">Setup</a>
|
||||
<a href="javascript:show('setup')" id="setupButton"></a>
|
||||
<ul id="setup">
|
||||
<li><a target="text" href="setup.html#pre">Prerequisites</a>
|
||||
<ul>
|
||||
<li><a target="text" href="epics3_13.html">Using EPICS 3.13</a></li>
|
||||
</ul></li>
|
||||
<li><a target="text" href="setup.html#lib">Build Library</a></li>
|
||||
<li><a target="text" href="setup.html#app">Build Application</a></li>
|
||||
<li><a target="text" href="setup.html#sta">Startup Script</a></li>
|
||||
<li><a target="text" href="setup.html#pro">Protocol File</a>
|
||||
<ul>
|
||||
<li><a target="text" href="setup.html#reload">Reloading</a></li>
|
||||
</ul></li>
|
||||
<li><a target="text" href="setup.html#rec">Records</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="top">
|
||||
<a target="text" href="protocol.html">Protocol Files</a>
|
||||
<a href="javascript:show('protocol')" id="protocolButton"></a>
|
||||
<ul id="protocol">
|
||||
<li><a target="text" href="protocol.html#gen">General</a></li>
|
||||
<li><a target="text" href="protocol.html#proto">Protocols</a></li>
|
||||
<li><a target="text" href="protocol.html#cmd">Commands</a></li>
|
||||
<li><a target="text" href="protocol.html#str">Strings</a></li>
|
||||
<li><a target="text" href="protocol.html#var">Variables</a>
|
||||
<ul>
|
||||
<li><a target="text" href="protocol.html#sysvar">System variables</a></li>
|
||||
<li><a target="text" href="protocol.html#argvar">Protocol Arguments</a></li>
|
||||
<li><a target="text" href="protocol.html#usrvar">User variables</a></li>
|
||||
</ul></li>
|
||||
<li><a target="text" href="protocol.html#except">Exception Handlers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="top">
|
||||
<a target="text" href="formats.html">Format Converters</a>
|
||||
<a href="javascript:show('formats')" id="formatsButton"></a>
|
||||
<ul id="formats">
|
||||
<li><a target="text" href="formats.html#syntax">Syntax</a></li>
|
||||
<li><a target="text" href="formats.html#types">Types & Fields</a></li>
|
||||
<li><a target="text" href="formats.html#stdd">%f %e %g</a></li>
|
||||
<li><a target="text" href="formats.html#stdl">%i %d %u %o %x</a></li>
|
||||
<li><a target="text" href="formats.html#stds">%s %c</a></li>
|
||||
<li><a target="text" href="formats.html#cset">%[<em>charset</em>]</a></li>
|
||||
<li><a target="text" href="formats.html#enum">%{<em>s0</em>|<em>s1</em>|...}</a></li>
|
||||
<li><a target="text" href="formats.html#bin">%b %B</a></li>
|
||||
<li><a target="text" href="formats.html#raw">%r</a></li>
|
||||
<li><a target="text" href="formats.html#bcd">%D</a></li>
|
||||
<li><a target="text" href="formats.html#chksum">%<<em>checksum</em>></a></li>
|
||||
<li><a target="text" href="formats.html#regex">%/<em>regex</em>/</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="top">
|
||||
<a target="text" href="processing.html">Record Processing</a>
|
||||
<a href="javascript:show('proc')" id="procButton"></a>
|
||||
<ul id="proc">
|
||||
<li><a target="text" href="processing.html#proc">Normal Processing</a></li>
|
||||
<li><a target="text" href="processing.html#init">Initialization</a></li>
|
||||
<li><a target="text" href="processing.html#iointr">I/O Intr</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="top">
|
||||
<a target="text" href="recordtypes.html">Record Types</a>
|
||||
<a href="javascript:show('records')" id="recordsButton"></a>
|
||||
<ul id="records" class="sub">
|
||||
<li><a target="text" href="aai.html">aai</a></li>
|
||||
<li><a target="text" href="aao.html">aao</a></li>
|
||||
<li><a target="text" href="ai.html">ai</a></li>
|
||||
<li><a target="text" href="ao.html">ao</a></li>
|
||||
<li><a target="text" href="bi.html">bi</a></li>
|
||||
<li><a target="text" href="bo.html">bo</a></li>
|
||||
<li><a target="text" href="mbbi.html">mbbi</a></li>
|
||||
<li><a target="text" href="mbbo.html">mbbo</a></li>
|
||||
<li><a target="text" href="mbbiDirect.html">mbbiDirect</a></li>
|
||||
<li><a target="text" href="mbboDirect.html">mbboDirect</a></li>
|
||||
<li><a target="text" href="stringin.html">stringin</a></li>
|
||||
<li><a target="text" href="stringout.html">stringout</a></li>
|
||||
<li><a target="text" href="longin.html">longin</a></li>
|
||||
<li><a target="text" href="longout.html">longout</a></li>
|
||||
<li><a target="text" href="waveform.html">waveform</a></li>
|
||||
<li><a target="text" href="calcout.html">calcout</a></li>
|
||||
<li><a target="text" href="scalcout.html">scalcout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="top">
|
||||
<a target="text" href="tipsandtricks.html">Tips & Tricks</a>
|
||||
<a href="javascript:show('tipsandtricks')" id="tipsandtricksButton"></a>
|
||||
<ul id="tipsandtricks">
|
||||
<li><a target="text" href="tipsandtricks.html#argvar">Many almost identical protocols</a></li>
|
||||
<li><a target="text" href="tipsandtricks.html#iointr">Read unsolicited input</a></li>
|
||||
<li><a target="text" href="tipsandtricks.html#multiline">Read multi-line messages</a></li>
|
||||
<li><a target="text" href="tipsandtricks.html#writemany">Write more than one value in one message</a></li>
|
||||
<li><a target="text" href="tipsandtricks.html#readmany">Read more than one value from one message</a></li>
|
||||
<li><a target="text" href="tipsandtricks.html#mixed">Read values of mixed data type</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h1>Programmer's Guide</h1>
|
||||
<div class="top">
|
||||
<a target="text" href="recordinterface.html">Record API</a>
|
||||
</div>
|
||||
<div class="top">
|
||||
<a target="text" href="businterface.html">Bus API</a>
|
||||
<a href="javascript:show('bus')" id="busButton"></a>
|
||||
<ul id="bus">
|
||||
<li><a target="text" href="businterface.html#class">Interface Class</a></li>
|
||||
<li><a target="text" href="businterface.html#theory">Theory of Operation</a>
|
||||
<ul>
|
||||
<li><a target="text" href="businterface.html#registration">Registration</a></li>
|
||||
<li><a target="text" href="businterface.html#create">Creation & deletion</a></li>
|
||||
<li><a target="text" href="businterface.html#connect">Connecting</a></li>
|
||||
<li><a target="text" href="businterface.html#lock">Bus locking</a></li>
|
||||
<li><a target="text" href="businterface.html#write">Writing output</a></li>
|
||||
<li><a target="text" href="businterface.html#read">Reading input</a></li>
|
||||
<li><a target="text" href="businterface.html#event">Handling events</a></li>
|
||||
</ul></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="top">
|
||||
<a target="text" href="formatconverter.html">Format Converter API</a>
|
||||
</div>
|
||||
<div class="top">
|
||||
<a target="text" href="osinterface.html">Operating System API</a>
|
||||
<body>
|
||||
<h1>User's Guide</h1>
|
||||
<div>
|
||||
<a target="_parent" href="index.html">Intro</a>
|
||||
</div>
|
||||
<div>
|
||||
<a target="_parent" href="setup.html">Setup</a>
|
||||
<a href="javascript:show('setup')" id="setupButton"></a>
|
||||
<div>
|
||||
<a target="_parent" href="setup.html#pre">Prerequisites</a>
|
||||
<div>
|
||||
<a target="_parent" href="epics3_13.html">Using EPICS 3.13</a>
|
||||
</div>
|
||||
<a target="_parent" href="setup.html#lib">Build Library</a>
|
||||
<a target="_parent" href="setup.html#app">Build Application</a>
|
||||
<a target="_parent" href="setup.html#sta">Startup Script</a>
|
||||
<a target="_parent" href="setup.html#pro">Protocol File</a>
|
||||
<div>
|
||||
<a target="_parent" href="setup.html#reload">Reloading</a>
|
||||
</div>
|
||||
<a target="_parent" href="setup.html#rec">Records</a>
|
||||
</div>
|
||||
</div>
|
||||
<p align="center" style="margin-top:20px;">
|
||||
<script type="text/javascript">
|
||||
<!--
|
||||
document.writeln('<a href="javascript:printview()">' +
|
||||
'<img src="printer.gif" alt="Printer friendly" border=0><br>' +
|
||||
'<small>Printer friendly<\/small><\/a>');
|
||||
hide('setup');
|
||||
hide('protocol');
|
||||
hide('formats');
|
||||
hide('proc');
|
||||
hide('records');
|
||||
hide('bus');
|
||||
hide('tipsandtricks');
|
||||
chapter=decodeURIComponent(parent.location.search.substr(1));
|
||||
if (chapter) parent.text.location.href=chapter;
|
||||
//-->
|
||||
</script>
|
||||
</p>
|
||||
<div>
|
||||
<a target="_parent" href="protocol.html">Protocol Files</a>
|
||||
<a href="javascript:show('protocol')" id="protocolButton"></a>
|
||||
<div>
|
||||
<a target="_parent" href="protocol.html#gen">General</a>
|
||||
<a target="_parent" href="protocol.html#proto">Protocols</a>
|
||||
<a target="_parent" href="protocol.html#cmd">Commands</a>
|
||||
<a target="_parent" href="protocol.html#str">Strings</a>
|
||||
<a target="_parent" href="protocol.html#var">Variables</a>
|
||||
<div>
|
||||
<a target="_parent" href="protocol.html#sysvar">System variables</a>
|
||||
<a target="_parent" href="protocol.html#argvar">Protocol Arguments</a>
|
||||
<a target="_parent" href="protocol.html#usrvar">User variables</a>
|
||||
</div>
|
||||
<a target="_parent" href="protocol.html#except">Exception Handlers</a>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a target="_parent" href="formats.html">Format Converters</a>
|
||||
<a href="javascript:show('formats')" id="formatsButton"></a>
|
||||
<div>
|
||||
<a target="_parent" href="formats.html#syntax">Syntax</a>
|
||||
<a target="_parent" href="formats.html#types">Types & Fields</a>
|
||||
<a target="_parent" href="formats.html#stdd" title="Standard DOUBLE converters">%f %e %g</a>
|
||||
<a target="_parent" href="formats.html#stdl" title="Standard LONG converters">%i %d %u %o %x</a>
|
||||
<a target="_parent" href="formats.html#stds" title="Standard STRING converters">%s %c</a>
|
||||
<a target="_parent" href="formats.html#cset" title="Character set STRING converter">%[<em>charset</em>]</a>
|
||||
<a target="_parent" href="formats.html#enum" title="Enumeration LONG converter">%{<em>s0</em>|<em>s1</em>|...}</a>
|
||||
<a target="_parent" href="formats.html#bin" title="Binary LONG converter">%b %B</a>
|
||||
<a target="_parent" href="formats.html#raw" title="Raw LONG converter">%r</a>
|
||||
<a target="_parent" href="formats.html#rawdouble" title="Raw DOUBLE converter">%R</a>
|
||||
<a target="_parent" href="formats.html#bcd" title="Binary coded decimal LONG converter">%D</a>
|
||||
<a target="_parent" href="formats.html#chksum" title="Checksum pseudo converter">%<<em>checksum</em>></a>
|
||||
<a target="_parent" href="formats.html#regex" title="Perl regular expression STRING converter">%/<em>regex</em>/</a>
|
||||
<a target="_parent" href="formats.html#mantexp" title="MantissaExponent DOUBLE converter">%m</a>
|
||||
<a target="_parent" href="formats.html#timestamp" title="Timestamp DOUBLE converter">%T</a>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a target="_parent" href="processing.html">Record Processing</a>
|
||||
<a href="javascript:show('proc')" id="procButton"></a>
|
||||
<div>
|
||||
<a target="_parent" href="processing.html#proc">Normal Processing</a>
|
||||
<a target="_parent" href="processing.html#init">Initialization</a>
|
||||
<a target="_parent" href="processing.html#iointr">I/O Intr</a>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a target="_parent" href="recordtypes.html">Record Types</a>
|
||||
<a href="javascript:show('records')" id="recordsButton"></a>
|
||||
<div>
|
||||
<a target="_parent" href="aai.html">aai</a>
|
||||
<a target="_parent" href="aao.html">aao</a>
|
||||
<a target="_parent" href="ai.html">ai</a>
|
||||
<a target="_parent" href="ao.html">ao</a>
|
||||
<a target="_parent" href="bi.html">bi</a>
|
||||
<a target="_parent" href="bo.html">bo</a>
|
||||
<a target="_parent" href="mbbi.html">mbbi</a>
|
||||
<a target="_parent" href="mbbo.html">mbbo</a>
|
||||
<a target="_parent" href="mbbiDirect.html">mbbiDirect</a>
|
||||
<a target="_parent" href="mbboDirect.html">mbboDirect</a>
|
||||
<a target="_parent" href="stringin.html">stringin</a>
|
||||
<a target="_parent" href="stringout.html">stringout</a>
|
||||
<a target="_parent" href="longin.html">longin</a>
|
||||
<a target="_parent" href="longout.html">longout</a>
|
||||
<a target="_parent" href="waveform.html">waveform</a>
|
||||
<a target="_parent" href="calcout.html">calcout</a>
|
||||
<a target="_parent" href="scalcout.html">scalcout</a>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a target="_parent" href="tipsandtricks.html">Tips & Tricks</a>
|
||||
<a href="javascript:show('tipsandtricks')" id="tipsandtricksButton"></a>
|
||||
<div>
|
||||
<a target="_parent" href="tipsandtricks.html#argvar">Many almost identical protocols</a>
|
||||
<a target="_parent" href="tipsandtricks.html#iointr">Read unsolicited input</a>
|
||||
<a target="_parent" href="tipsandtricks.html#multiline">Read multi-line messages</a>
|
||||
<a target="_parent" href="tipsandtricks.html#writemany">Write more than one value in one message</a>
|
||||
<a target="_parent" href="tipsandtricks.html#readmany">Read more than one value from one message</a>
|
||||
<a target="_parent" href="tipsandtricks.html#mixed">Read values of mixed data type</a>
|
||||
<a target="_parent" href="tipsandtricks.html#web">Read a web page</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Programmer's Guide</h1>
|
||||
<div>
|
||||
<a target="_parent" href="recordinterface.html">Record API</a>
|
||||
</div>
|
||||
<div>
|
||||
<a target="_parent" href="businterface.html">Bus API</a>
|
||||
<a href="javascript:show('bus')" id="busButton"></a>
|
||||
<div>
|
||||
<a target="_parent" href="businterface.html#class">Interface Class</a>
|
||||
<a target="_parent" href="businterface.html#theory">Theory of Operation</a>
|
||||
<div>
|
||||
<a target="_parent" href="businterface.html#registration">Registration</a>
|
||||
<a target="_parent" href="businterface.html#create">Creation & deletion</a>
|
||||
<a target="_parent" href="businterface.html#connect">Connecting</a>
|
||||
<a target="_parent" href="businterface.html#lock">Bus locking</a>
|
||||
<a target="_parent" href="businterface.html#write">Writing output</a>
|
||||
<a target="_parent" href="businterface.html#read">Reading input</a>
|
||||
<a target="_parent" href="businterface.html#event">Handling events</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a target="_parent" href="formatconverter.html">Format Converter API</a>
|
||||
</div>
|
||||
<div>
|
||||
<a target="_parent" href="osinterface.html">Operating System API</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
@ -3,18 +3,18 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: Operating System API</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: Operating System API</h1>
|
||||
|
||||
<h2>Sorry, this documentation is still missing.</h2>
|
||||
|
||||
<hr>
|
||||
<p><small>Dirk Zimoch, 2006</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
BIN
doc/printer.gif
Before Width: | Height: | Size: 1.2 KiB |
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: Record Processing</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: Record Processing</h1>
|
||||
|
||||
<a name="proc"></a>
|
||||
@ -239,6 +240,5 @@ with "<code>ROI</code>", followed by two floating point numbers.
|
||||
<hr>
|
||||
<p align="right"><a href="recordtypes.html">Next: Supported Record Types</a></p>
|
||||
<p><small>Dirk Zimoch, 2005</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: Protocol Files</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: Protocol Files</h1>
|
||||
<a name="gen"></a>
|
||||
<h2>1. General Information</h2>
|
||||
@ -241,8 +242,12 @@ that hex value.<br>
|
||||
that octal value.<br>
|
||||
<code>\1</code> to <code>\9</code> followed by up to two more decimal
|
||||
digits means a byte with that decimal value.<br>
|
||||
<code>\?</code> in the argument string of an <code>in</code>
|
||||
<a href="#cmd">command</a> matches any input byte<br>
|
||||
<code>\?</code> in input matches any byte, in output it does not print
|
||||
anything.<br>
|
||||
<span class="new">
|
||||
<code>\_</code> in input matches any amount of white space (including none),
|
||||
in output it prints a single space.<br>
|
||||
</span>
|
||||
<code>\$</code> followed by the name of a
|
||||
<a href="#var">protocol varible</a> is replaced by the contents of that
|
||||
variable.
|
||||
@ -251,29 +256,54 @@ variable.
|
||||
For non-printable characters, it is often easier to write sequences of
|
||||
byte values instead of escaped quoted string literals.
|
||||
A byte is written as an unquoted decimal, hexadecimal, or octal
|
||||
number in the range of -128 to 255 (-0x80 to 0xff, -0200 to 0377).
|
||||
<em>StreamDevice</em> also defines some symbolic names for frequently
|
||||
used byte codes as aliases for the numeric byte value:<br>
|
||||
<code>EOT</code> means <em>end of transmission</em> (ASCII code 4).<br>
|
||||
<code>ACK</code> means <em>acknowledge</em> (ASCII code 6).<br>
|
||||
<code>BEL</code> means <em>bell</em> (ASCII code 7).<br>
|
||||
<code>BS</code> means <em>backspace</em> (ASCII code 8).<br>
|
||||
<code>HT</code> or <code>TAB</code> mean <em>horizontal tabulator</em>
|
||||
(ASCII code 9).<br>
|
||||
<code>LF</code> or <code>NL</code> mean <em>line feed</em> /
|
||||
<em>new line</em> (ASCII code 10).<br>
|
||||
<code>CR</code> means <em>carriage return</em> (ASCII code 13).<br>
|
||||
<code>ESC</code> means <em>escape</em> (ASCII code 27).<br>
|
||||
<code>DEL</code> means <em>delete</em> (ASCII code 127).<br>
|
||||
<code>SKIP</code> in the argument string of an <code>in</code>
|
||||
<a href="#cmd">command</a> matches any input byte.
|
||||
number in the range of <code>-128</code> to <code>255</code>,
|
||||
<code>-0x80</code> to <code>0xff</code> (not case sesitive),
|
||||
or <code>-0200</code> to <code>0377</code>, respectively.
|
||||
</p>
|
||||
<p>
|
||||
<em>StreamDevice</em> also recordgnizes the ASCII symbolic names
|
||||
(not case sensitive) for several byte codes:<br>
|
||||
<code>NUL </code>(= <code>0x00</code>) <em>null</em><br>
|
||||
<code>SOH </code>(= <code>0x01</code>) <em>start of heading</em><br>
|
||||
<code>STX </code>(= <code>0x02</code>) <em>start of text</em><br>
|
||||
<code>ETX </code>(= <code>0x03</code>) <em>end of text</em><br>
|
||||
<code>EOT </code>(= <code>0x04</code>) <em>end of transmission</em><br>
|
||||
<code>ENQ </code>(= <code>0x05</code>) <em>enquiry</em><br>
|
||||
<code>ACK </code>(= <code>0x06</code>) <em>acknowledge</em><br>
|
||||
<code>BEL </code>(= <code>0x07</code>) <em>bell</em><br>
|
||||
<code>BS </code>(= <code>0x08</code>) <em>backspace</em><br>
|
||||
<code>HT</code> or <code>TAB</code> (= <code>0x09</code>) <em>horizontal tabulator</em><br>
|
||||
<code>LF</code> or <code>NL</code> (= <code>0x0A</code> or <code>10</code>) <em>line feed / new line</em><br>
|
||||
<code>VT </code>(= <code>0x0B</code> or <code>11</code>) <em>vertical tabulator</em><br>
|
||||
<code>FF</code> or <code>NP</code> (= <code>0x0C</code> or <code>12</code>) <em>form feed / new page</em><br>
|
||||
<code>CR </code>(= <code>0x0D</code> or <code>13</code>) <em>carriage return</em><br>
|
||||
<code>SO </code>(= <code>0x0E</code> or <code>14</code>) <em>shift out</em><br>
|
||||
<code>SI </code>(= <code>0x0F</code> or <code>15</code>) <em>shift in</em><br>
|
||||
<code>DLE </code>(= <code>0x10</code> or <code>16</code>) <em>data link escape</em><br>
|
||||
<code>DC1 </code>(= <code>0x11</code> or <code>17</code>) <em>device control 1</em><br>
|
||||
<code>DC2 </code>(= <code>0x12</code> or <code>18</code>) <em>device control 2</em><br>
|
||||
<code>DC3 </code>(= <code>0x13</code> or <code>19</code>) <em>device control 3</em><br>
|
||||
<code>DC4 </code>(= <code>0x14</code> or <code>20</code>) <em>device control 4</em><br>
|
||||
<code>NAK </code>(= <code>0x15</code> or <code>21</code>) <em>negative acknowledge</em><br>
|
||||
<code>SYN </code>(= <code>0x16</code> or <code>22</code>) <em>synchronous idle</em><br>
|
||||
<code>ETB </code>(= <code>0x17</code> or <code>23</code>) <em>end of transmission block</em><br>
|
||||
<code>CAN </code>(= <code>0x18</code> or <code>24</code>) <em>cancel</em><br>
|
||||
<code>EM </code>(= <code>0x19</code> or <code>25</code>) <em>end of medium</em><br>
|
||||
<code>SUB </code>(= <code>0x1A</code> or <code>26</code>) <em></em>substitute<br>
|
||||
<code>ESC </code>(= <code>0x1B</code> or <code>27</code>) <em>escape</em><br>
|
||||
<code>FS </code>(= <code>0x1C</code> or <code>28</code>) <em>file separator</em><br>
|
||||
<code>GS </code>(= <code>0x1D</code> or <code>29</code>) <em>group separator</em><br>
|
||||
<code>RS </code>(= <code>0x1E</code> or <code>30</code>) <em>record separator</em><br>
|
||||
<code>US </code>(= <code>0x1F</code> or <code>31</code>) <em>unit separator</em><br>
|
||||
<code>DEL </code>(= <code>0x7F</code> or <code>127</code>) <em>delete</em><br>
|
||||
<code>SKIP</code> or <code>?</code> matches any input byte
|
||||
</p>
|
||||
<p>
|
||||
A single string can be built from several quoted literals and byte values
|
||||
by writing them separated by whitespaces or comma.
|
||||
</p>
|
||||
|
||||
<h3>Example:</h3>
|
||||
<h3>Examples:</h3>
|
||||
<p>
|
||||
The following lines represent the same string:<br>
|
||||
<code>"Hello world\r\n"</code><br>
|
||||
@ -408,9 +438,12 @@ what they influence.
|
||||
(see <a href="formats.html">formats</a> and
|
||||
<a href="waveform.html">waveform record</a>), what string
|
||||
to write or to expect between values?
|
||||
If the first character of the <code>Separator</code> is a
|
||||
<span class="new">
|
||||
<strike>If the first character of the <code>Separator</code> is a
|
||||
space, it matches any number of any whitespace characters in
|
||||
an <code>in</code> command.
|
||||
an <code>in</code> command.</strike> To match arbitrary amount of
|
||||
whitespace in input, use <code>"\_"</code>.
|
||||
</span>
|
||||
</dd>
|
||||
<dt><code>ExtraInput = Error;</code></dt>
|
||||
<dd>
|
||||
@ -546,7 +579,6 @@ settings from the protocol in which the exception occurred.
|
||||
</p>
|
||||
<hr>
|
||||
<p align="right"><a href="formats.html">Next: Format Converters</a></p>
|
||||
<p><small>Dirk Zimoch, 2006</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
<p><small>Dirk Zimoch, 2011</small></p>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,18 +3,18 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: Record API</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: Record API</h1>
|
||||
|
||||
<h2>Sorry, this documentation is still missing.</h2>
|
||||
|
||||
<hr>
|
||||
<p><small>Dirk Zimoch, 2006</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: Record Types</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: Record Types</h1>
|
||||
|
||||
<h2>Supported Record Types</h2>
|
||||
@ -49,6 +50,5 @@ It is also possible to
|
||||
|
||||
<hr>
|
||||
<p><small>Dirk Zimoch, 2005</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: scalcout Records</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: scalcout Records</h1>
|
||||
|
||||
<p>
|
||||
@ -96,6 +97,5 @@ Different record fields are used for output and input. The variable
|
||||
<a href="calcout.html">calcout</a>
|
||||
</p>
|
||||
<p><small>Dirk Zimoch, 2005</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
192
doc/setup.html
@ -3,79 +3,159 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: Setup</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: Setup</h1>
|
||||
|
||||
<a name="pre"></a>
|
||||
<h2>1. Prerequisites</h2>
|
||||
<p>
|
||||
<em>StreamDevice</em> requires either
|
||||
<em>StreamDevice</em> requires either
|
||||
<a href="http://www.aps.anl.gov/epics/base/R3-14/index.php"
|
||||
target="ex">EPICS base R3.14.6 or higher</a> or
|
||||
<a href="http://www.aps.anl.gov/epics/base/R3-13.php"
|
||||
target="ex">EPICS base R3.13.7 or higher</a>.
|
||||
How to use <em>StreamDevice</em> on EPICS R3.13 is described on a
|
||||
<a href="epics3_13.html">separate page</a>.
|
||||
Because <em>StreamDevice</em> comes with an interface to
|
||||
<a href="http://www.aps.anl.gov/epics/modules/soft/asyn/"
|
||||
target="ex"><em>asynDriver</em> version R4-3 or higher</a> as the
|
||||
underlying driver layer,
|
||||
you should have <em>asynDriver</em> installed first.
|
||||
</p>
|
||||
<p>
|
||||
<em>StreamDevice</em> has support for the
|
||||
<a href="scalcout.html"><em>scalcout</em></a> record from the
|
||||
<em>calc</em> module of <a target="ex"
|
||||
href="http://www.aps.anl.gov/aod/bcda/synApps/index.php"
|
||||
><em>synApps</em></a>.
|
||||
Up to <em>calc</em> release R2-6 (<em>synApps</em> release R5_1),
|
||||
the <em>scalcout</em> record needs a fix.
|
||||
(See separate <a href="scalcout.html"><em>scalcout</em> page</a>.)
|
||||
Support for the scalcout is optional. <em>StreamDevice</em> works
|
||||
as well without scalcout or SynApps.
|
||||
</p>
|
||||
<h3>Fix required for base R3.14.8.2 and earlier on Windows</h3>
|
||||
<p>
|
||||
Up to release R3.14.8.2, a fix in EPICS base is required to build
|
||||
<em>StreamDevice</em> on Windows (not cygwin).
|
||||
In <kbd>src/iocsh/iocsh.h</kbd>, add the following line
|
||||
Add the following line to <kbd>src/iocsh/iocsh.h</kbd>
|
||||
and rebuild base.
|
||||
</p>
|
||||
<pre>
|
||||
epicsShareFunc int epicsShareAPI iocshCmd(const char *command);
|
||||
</pre>
|
||||
|
||||
<h3>Configuration</h3>
|
||||
<p>
|
||||
Make sure that the <em>asyn</em> library (and the <em>calc</em> module of
|
||||
<em>synApps</em>, if desired) can be found, e.g. by
|
||||
adding <code>ASYN</code>
|
||||
and (if installed) <code>CALC</code> or <code>SYNAPPS</code>
|
||||
to your <kbd><top>/configure/RELEASE</kbd> file:
|
||||
<em>StreamDevice</em> does not come with its own <kbd><top></kbd>
|
||||
location and <kbd><top>/configure</kbd> directory.
|
||||
It expects to be put into an already existing <kbd><top></kbd>
|
||||
directory structure.
|
||||
You can simply create one with <code>makeBaseApp.pl</code>
|
||||
(which is part of EPICS base):
|
||||
</p>
|
||||
<p>
|
||||
<code>mkdir top</code><br>
|
||||
<code>cd top</code><br>
|
||||
<code>makeBaseApp.pl -t support</code>
|
||||
</p>
|
||||
<p>
|
||||
When asked for "Application names" just hit Enter.
|
||||
Then go to the newly created <kbd>configure</kbd> subdirectory and
|
||||
edit the <kbd>RELEASE</kbd> file you find there according to the
|
||||
instructions below.
|
||||
</p>
|
||||
<p>
|
||||
After changing any configuration, you should run <code>make</code>
|
||||
in this directory.
|
||||
</p>
|
||||
<p>
|
||||
For details on <kbd><top></kbd> directories and <kbd>RELEASE</kbd>
|
||||
files please refer to the
|
||||
<a href="http://www.aps.anl.gov/epics/base/R3-14/8-docs/AppDevGuide.pdf"
|
||||
target="ex"><em>IOC Application Developer's Guide</em></a> chapter 4:
|
||||
EPICS Build Facility.
|
||||
</p>
|
||||
|
||||
<h4>Support for <em>asynDriver</em></h4>
|
||||
<p>
|
||||
You most probably want <em>asynDriver</em> support included, because that is the
|
||||
standard way for <em>StreamDevice</em> to talk to hardware.
|
||||
First get and install <a href="http://www.aps.anl.gov/epics/modules/soft/asyn/"
|
||||
target="ex"><em>asynDriver</em></a> version 4-3 or higher before you build <em>StreamDevice</em>.
|
||||
I have tested <em>StreamDevice</em> with <em>asynDriver</em> versions up to 4-17.
|
||||
Make sure that the <em>asyn</em> library can be found by adding the path to the
|
||||
<em><top></em> directory of your <em>asyn</em> installation to the
|
||||
<kbd>RELEASE</kbd> file:
|
||||
<pre>
|
||||
ASYN=/home/epics/asyn/4-5
|
||||
CALC=/home/epics/synApps/calc/2-7
|
||||
ASYN=/home/epics/asyn4-17
|
||||
</pre>
|
||||
|
||||
<h4>Support for sCalcout record</h4>
|
||||
<p>
|
||||
The <a target="ex"
|
||||
href="http://www.aps.anl.gov/bcda/synApps/calc/R2-8/sCalcoutRecord.html"
|
||||
><em>sCalcout</em></a> record is part of <a target="ex"
|
||||
href="http://www.aps.anl.gov/aod/bcda/synApps/index.php"
|
||||
><em>synApps</em></a>.
|
||||
If <em>streamDevice</em> should be built with support for this record,
|
||||
you have to install the <em>calc</em> module from <em>SynApps</em> first.
|
||||
Add references to the <kbd>RELEASE</kbd> file as shown here:
|
||||
<pre>
|
||||
CALC=/home/epics/synApps/calc-2-8
|
||||
</pre>
|
||||
<p>
|
||||
Up to <em>calc</em> release R2-6 (<em>synApps</em> release R5_1),
|
||||
the <em>sCalcout</em> record needs a fix.
|
||||
(See separate <a href="scalcout.html"><em>scalcout</em> page</a>.)
|
||||
And the <em>calc</em> modue had dependencies on other <em>SynApps</em>
|
||||
modules. Release R2-8 or newer is recommended.
|
||||
</p>
|
||||
<p>
|
||||
Support for the scalcout is optional. <em>StreamDevice</em> works
|
||||
as well without scalcout or SynApps.
|
||||
</p>
|
||||
|
||||
<h4>Support for regular expression matching</h4>
|
||||
<p>
|
||||
If you want to enable regular expression matching, you need the <em>PCRE</em> package.
|
||||
For most Linux systems, it is already installed. In that case add the locations
|
||||
of the <em>PCRE</em> header and library to your <kbd>RELEASE</kbd> file:
|
||||
For most Linux systems, it is already installed.
|
||||
In that case add the locations you have to make the locations of the
|
||||
<em>PCRE</em> header file and library known.
|
||||
However, the pre-installed package can only by used for the host architecture.
|
||||
Thus, add them not to <kbd>RELEASE</kbd> but to <kbd>RELEASE.Common.linux-x86</kbd>
|
||||
(if linux-x86 is your EPICS_HOST_ARCH).
|
||||
Note that different Linux distributions may locate the files in different directories.
|
||||
</p>
|
||||
<pre>
|
||||
PCRE_INCLUDE=/usr/include/pcre
|
||||
PCRE_LIB=/usr/lib
|
||||
</pre>
|
||||
<p>
|
||||
If you want to build <em>StreamDevice</em> for platforms without <em>PCRE</em> support,
|
||||
it is the easiest to build <em>PCRE</em> as an EPICS application.
|
||||
Download the <em>PCRE</em> package from <a target=ex href="http://www.pcre.org">www.pcre.org</a>
|
||||
and compile it with my EPICS compatible
|
||||
<a target=ex href="http://epics.web.psi.ch/software/streamdevice/pcre/Makefile">Makefile</a>.
|
||||
Then define the location of the application in your RELEASE file.
|
||||
A pre-compiled Windows version of <em>PCRE</em> is available at
|
||||
<a href="http://sourceforge.net/projects/gnuwin32/files/pcre/7.0/pcre-7.0.exe/download"
|
||||
target="ex">sourceforge</a>
|
||||
</p>
|
||||
<p>
|
||||
If you want to have <em>PCRE</em> support on platforms that don't support it natively,
|
||||
e.g. vxWorks, it is probably the easiest to build <em>PCRE</em> as an EPICS application.
|
||||
</p>
|
||||
<p>
|
||||
<h4>Building the <em>PCRE</em> package as an EPICS module</h4>
|
||||
<p>
|
||||
<ol>
|
||||
<li>
|
||||
Download the <em>PCRE</em> package from <a target=ex href="http://www.pcre.org">www.pcre.org</a>.
|
||||
</li>
|
||||
<li>
|
||||
Extract the <em>PCRE</em> package in the <kbd><top></kbd> directory of
|
||||
<em>StreamDevice</em> or create a separate <kbd><top></kbd> location using
|
||||
<code>makeBaseApp.pl</code>.
|
||||
</li>
|
||||
<li>
|
||||
Download this <a target=ex href="http://epics.web.psi.ch/software/streamdevice/pcre/Makefile"
|
||||
>Makefile</a> and this
|
||||
<a target=ex href="http://epics.web.psi.ch/software/streamdevice/pcre/fixforvxworks.pl"
|
||||
>fixforvxworks.pl</a> script and save them to the extracted pcre directory.
|
||||
</li>
|
||||
<li>
|
||||
Change into the pcre direcrory and run <code>perl fixforvxworks.pl</code>
|
||||
</li>
|
||||
<li>
|
||||
Run <code>make</code> (or <code>gmake</code>)
|
||||
</li>
|
||||
</ol>
|
||||
<p>
|
||||
Define the location of the pcre <kbd><top></kbd> in the RELEASE file for <em>StreamDevice</em>.
|
||||
</p>
|
||||
<pre>
|
||||
PCRE=/home/epics/pcre
|
||||
@ -84,25 +164,17 @@ PCRE=/home/epics/pcre
|
||||
Regular expressions are optional. If you don't want them, you don't need this.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
For details on <kbd><top></kbd> directories and RELEASE files,
|
||||
please refer to the
|
||||
<a href="http://www.aps.anl.gov/epics/base/R3-14/8-docs/AppDevGuide.pdf"
|
||||
target="ex"><em>IOC Application Developer's Guide</em></a> chapter 4:
|
||||
EPICS Build Facility.
|
||||
</p>
|
||||
|
||||
<a name="lib"></a>
|
||||
<h2>2. Build the <em>StreamDevice</em> Library</h2>
|
||||
<p>
|
||||
Unpack the
|
||||
<a href="http://epics.web.psi.ch/software/streamdevice/StreamDevice-2.tgz"
|
||||
><em>StreamDevice</em> package</a> in a <kbd><top></kbd> directory
|
||||
><em>StreamDevice</em> package</a> in the <kbd><top></kbd> directory
|
||||
of your application build area.
|
||||
(You might probably have done this already.)
|
||||
Go to the newly created <em>StreamDevice</em> directory
|
||||
and run <kbd>make</kbd> (or <kbd>gmake</kbd>).
|
||||
This will create and install the <em>stream</em> library and the
|
||||
and run <code>make</code> (or <code>gmake</code>).
|
||||
This will create and install the <em>stream</em> library and the
|
||||
<kbd>stream.dbd</kbd> file.
|
||||
</p>
|
||||
<a name="app"></a>
|
||||
@ -121,7 +193,8 @@ PROD_LIBS += asyn
|
||||
</pre>
|
||||
<p>
|
||||
Include the following lines in your xxxAppInclude.dbd file to use
|
||||
<em>stream</em> and <em>asyn</em> with serial lines and IP sockets:
|
||||
<em>stream</em> and <em>asyn</em> with serial lines, IP sockets,
|
||||
and vxi11 ("GPIB over ethernet") support.
|
||||
</p>
|
||||
<pre>
|
||||
include "base.dbd"
|
||||
@ -129,6 +202,7 @@ include "stream.dbd"
|
||||
include "asyn.dbd"
|
||||
registrar(drvAsynIPPortRegisterCommands)
|
||||
registrar(drvAsynSerialPortRegisterCommands)
|
||||
registrar(vxi11RegisterCommands)
|
||||
</pre>
|
||||
<p>
|
||||
You can find an example application in the <kbd>streamApp</kbd>
|
||||
@ -151,7 +225,7 @@ i.e. the current directory.
|
||||
<p>
|
||||
Also configure the buses (in <em>asynDriver</em> terms: ports) you want
|
||||
to use with <em>StreamDevice</em>.
|
||||
You can give the buses any name you want, like <kbd>COM1</kbd> or
|
||||
You can give the buses any name you want, like <kbd>COM1</kbd> or
|
||||
<kbd>socket</kbd>, but I recommend to use names related to the
|
||||
connected device.
|
||||
</p>
|
||||
@ -189,7 +263,7 @@ drvAsynIPPortConfigure ("PS1", "192.168.164.10:23")
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
With a VXI11 (GPIB via TCP/IP) connection, e.g. a
|
||||
With a VXI11 (GPIB via TCP/IP) connection, e.g. a
|
||||
HP E2050A on IP address 192.168.164.10, it would look like this:
|
||||
</p>
|
||||
<pre>
|
||||
@ -211,7 +285,7 @@ During <code>iocInit</code>, <em>streamDevice</em> loads and parses
|
||||
the required protocol files.
|
||||
If the files contain errors, they are printed on the IOC shell.
|
||||
Put the protocol file in one of the directories listed in
|
||||
<code>STREAM_PROTOCOL_PATH</code>.
|
||||
<code>STREAM_PROTOCOL_PATH</code>.
|
||||
</p>
|
||||
<h3>Example:</h3>
|
||||
<p>
|
||||
@ -234,7 +308,7 @@ Terminator = CR LF;
|
||||
|
||||
setCurrent {
|
||||
out "CURRENT %.2f";
|
||||
@init {
|
||||
@init {
|
||||
out "CURRENT?";
|
||||
in "CURRENT %f A";
|
||||
}
|
||||
@ -270,19 +344,24 @@ See the <a href="protocol.html">next chapter</a> for protocol files in depth.
|
||||
<p>
|
||||
To make a record use <em>StreamDevice</em>, set its <code>DTYP</code> field to
|
||||
<code>"stream"</code>.
|
||||
</p>
|
||||
<p>
|
||||
The <code>INP</code> or <code>OUT</code> link has the form
|
||||
<code>"@<var>file protocol bus</var> [<var>address</var> [<var>parameters</var>]]"</code>.
|
||||
</p>
|
||||
<p>
|
||||
Here, <code><var>file</var></code> is the name of the protocol file and
|
||||
<code><var>protocol</var></code> is the name of a protocol defined in this file.
|
||||
(See the <a href="protocol.html">next chapter</a>.)
|
||||
</p>
|
||||
<p>
|
||||
If the protocol requires <a href="protocol.html#argvar">arguments</a>,
|
||||
specify them enclosed in parentheses:
|
||||
<code><var>protocol</var>(<var>arg1,arg2,...</var>)</code>.
|
||||
</p>
|
||||
<p>
|
||||
The communication channel is specified with <code><var>bus</var></code> and
|
||||
<code><var>addr</var></code>.
|
||||
The communication channel is specified with <code><var>bus</var></code>
|
||||
(aka <em>asynDriver</em> "port") and <code><var>addr</var></code>.
|
||||
If the bus does not have addresses, <code><var>addr</var></code> is dispensable.
|
||||
Optional <code><var>parameters</var></code> are passed to the bus driver.
|
||||
</p>
|
||||
@ -298,8 +377,8 @@ The bus is called <em>PS1</em> like the device.
|
||||
record (ao, "PS1:I-set")
|
||||
{
|
||||
field (DESC, "Set current of PS1")
|
||||
field (DTYP, "stream")
|
||||
field (OUT, "@ExamplePS.proto setCurrent PS1")
|
||||
<b>field (DTYP, "stream")</b>
|
||||
<b>field (OUT, "@ExamplePS.proto setCurrent PS1")</b>
|
||||
field (EGU, "A")
|
||||
field (PREC, "2")
|
||||
field (DRVL, "0")
|
||||
@ -311,7 +390,6 @@ record (ao, "PS1:I-set")
|
||||
|
||||
<hr>
|
||||
<p align="right"><a href="protocol.html">Next: Protocol Files</a></p>
|
||||
<p><small>Dirk Zimoch, 2007</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
<p><small>Dirk Zimoch, 2011</small></p>
|
||||
</body>
|
||||
</html>
|
||||
|
BIN
doc/sls_icon.ico
Before Width: | Height: | Size: 318 B |
BIN
doc/space.gif
Before Width: | Height: | Size: 807 B |
@ -1,13 +1,17 @@
|
||||
a:link {color: #0000D0; text-decoration:none;}
|
||||
a:visited {color: #0000D0; text-decoration:none;}
|
||||
a:hover {color: #FF0000; text-decoration:none;}
|
||||
a:link {color: #0000D0;}
|
||||
a:visited {color: #0000D0;}
|
||||
a:hover {color: #FF0000;}
|
||||
|
||||
body {
|
||||
margin-right:20px;
|
||||
font-family:sans-serif;
|
||||
margin-right:1em;
|
||||
margin-left:15em;
|
||||
margin-top:11ex;
|
||||
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
|
||||
font-size:14px;
|
||||
background-color:#ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
a[name] { position:relative; top:-11ex;}
|
||||
|
||||
pre {
|
||||
background-color:#f4f4f4;
|
||||
@ -17,6 +21,10 @@ pre {
|
||||
margin:2ex;
|
||||
}
|
||||
|
||||
kbd {
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
dt {
|
||||
margin-top:0.5ex;
|
||||
}
|
||||
@ -26,6 +34,22 @@ h1 {
|
||||
margin-top:0;
|
||||
font-style:italic;
|
||||
font-family:serif;
|
||||
text-align:center;
|
||||
position:fixed;
|
||||
top:0;
|
||||
left:0;
|
||||
width:100%;
|
||||
min-height:60px;
|
||||
height: 4ex;
|
||||
line-height:190%;
|
||||
background-color:white;
|
||||
border-top:6px solid #1b4486;
|
||||
border-bottom:4px solid #1b4486;
|
||||
white-space:nowrap;
|
||||
background-image:url(PSI.gif);
|
||||
background-repeat:no-repeat;
|
||||
background-position:10px 2px;
|
||||
text-shadow: .1em .1em .1em lightgray;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@ -34,6 +58,11 @@ h2 {
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size:120%;
|
||||
margin-bottom:0.25ex;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size:100%;
|
||||
margin-bottom:0.25ex;
|
||||
}
|
||||
@ -58,22 +87,41 @@ code {
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
/*
|
||||
a[target=ex] {
|
||||
background-image:url(ex.png);
|
||||
background-repeat:no-repeat;
|
||||
background-position:right center;
|
||||
padding-right: 12px;
|
||||
}
|
||||
#navleft {
|
||||
position:fixed;
|
||||
left:0;
|
||||
top:0;
|
||||
padding-top:9ex;
|
||||
width:14em;
|
||||
height:100%;
|
||||
border-style:solid;
|
||||
border-color:black;
|
||||
border-width:0 1px 0 0;
|
||||
background-color:#e3eaf6;
|
||||
overflow:hidden;
|
||||
z-index:0;
|
||||
}
|
||||
|
||||
a[target=ex]:hover {
|
||||
background-image:url(exr.png);
|
||||
}
|
||||
*/
|
||||
.new {
|
||||
background-color: #ffc;
|
||||
}
|
||||
|
||||
a[target=ex]:after {
|
||||
content:" " url(ex.png);
|
||||
}
|
||||
|
||||
a[target=ex]:hover:after {
|
||||
content: " " url(exr.png);
|
||||
}
|
||||
|
||||
@media print {
|
||||
a:link {color: black; text-decoration:none;}
|
||||
a:visited {color: black; text-decoration:none;}
|
||||
a:hover {color: black; text-decoration:none;}
|
||||
a[target=ex] {text-decoration:underline;}
|
||||
a[target=ex]:after {content:" [" attr(href) "]";}
|
||||
code {color: black; }
|
||||
body {margin-left:10px;}
|
||||
h1 {position:absolute;}
|
||||
#navleft {display:none;}
|
||||
}
|
||||
|
@ -1,92 +0,0 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<h1>EPICS <em>StreamDevice</em> Documentation</h1>
|
||||
|
||||
<h2>What is <em>StreamDevice</em>?</h2>
|
||||
<p>
|
||||
<em>StreamDevice</em> is a generic
|
||||
<a href="http://www.aps.anl.gov/epics" target="ex">EPICS</a>
|
||||
device support for devices with a "byte stream" based
|
||||
communication interface.
|
||||
That means devices that can be controlled by sending and
|
||||
receiving strings (in the broadest sense, including non-printable
|
||||
characters and even null-bytes).
|
||||
Examples for this type of communication interface are
|
||||
serial line (RS-232, RS-485, ...),
|
||||
IEEE-488 (also known as GPIB or HP-IB), and TCP/IP.
|
||||
<em>StreamDevice</em> comes with an interface to<a target="ex"
|
||||
href="http://www.aps.anl.gov/epics/modules/soft/asyn/">
|
||||
<em>asynDriver</em></a>
|
||||
but can be extended to
|
||||
<a href="businterface.html">support other bus drivers</a>.
|
||||
</p>
|
||||
<p>
|
||||
If the device can be controlled with strings like
|
||||
"<code>RF:FREQ 499.655 MHZ</code>", <em>StreamDevice</em> can be used.
|
||||
How the strings exactly look like is defined in <em>protocols</em>.
|
||||
Formatting and interpretation of values is done with
|
||||
<a href="formats.html"><em>format converters</em></a> similar to those
|
||||
known from the C functions <em>printf()</em> and <em>scanf()</em>.
|
||||
To support other formats, it is possible to
|
||||
<a href="formatconverter.html">write your own converters</a>.
|
||||
</p>
|
||||
<p>
|
||||
Each record with <em>StreamDevice</em> support runs one <em>protocol</em>
|
||||
to read or write its value.
|
||||
All <em>protocols</em> are defined in
|
||||
<a href="protocol.html"><em>protocol files</em></a> in plain ASCII text.
|
||||
No compiling is necessary to change a protocol or to support new devices.
|
||||
<em>Protocols</em> can be as simple as just one output string or can
|
||||
consist of many strings sent to and read from the device.
|
||||
However, a protocol is linear.
|
||||
That means it runs from start to end each time the record is
|
||||
<a href="processing.html">processed</a>.
|
||||
It does not provide loops or branches.
|
||||
</p>
|
||||
<p>
|
||||
<em>StreamDevice</em> supports all
|
||||
<a href="recordtypes.html">standard records</a> of EPICS base
|
||||
which can have device support.
|
||||
It is also possible to
|
||||
<a href="recordinterface.html">write support for new record types</a>.
|
||||
</p>
|
||||
|
||||
<h2>What is <em>StreamDevice</em> not?</h2>
|
||||
<p>
|
||||
It is not a programming language for a high-level application.
|
||||
It is, for example, not possible to write a complete scanning program
|
||||
in a <em>protocol</em>.
|
||||
Use other tools for that and use <em>StreamDevice</em> only for the
|
||||
primitive commands.
|
||||
</p>
|
||||
<p>
|
||||
It is not a block oriented device support.
|
||||
It is not possible to send or receive huge blocks of data that contain
|
||||
many process variables distributed over many records.
|
||||
</p>
|
||||
|
||||
<h2>Recommended Readings</h2>
|
||||
<p>
|
||||
<a href="http://www.aps.anl.gov/epics/base/R3-14/8-docs/AppDevGuide.pdf"
|
||||
target="ex">IOC Application Developer's Guide (PDF)</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="http://www.aps.anl.gov/asd/controls/epics/EpicsDocumentation/AppDevManuals/RecordRef/Recordref-1.html"
|
||||
target="ex">EPICS Record Reference Manual</a>
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
<p align="right"><a href="setup.html">Next: Setup</a></p>
|
||||
<p><small>Dirk Zimoch, 2006</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,5 +0,0 @@
|
||||
if (parent.head) {
|
||||
parent.head.document.getElementById('subtitle').innerHTML=document.title;
|
||||
parent.document.title=document.title;
|
||||
document.getElementsByTagName('h1')[0].style.display="none";
|
||||
}
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: stringin Records</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: stringin Records</h1>
|
||||
|
||||
<h2>Normal Operation</h2>
|
||||
@ -62,6 +63,5 @@ written or read value.
|
||||
<a href="scalcout.html">scalcout</a>
|
||||
</p>
|
||||
<p><small>Dirk Zimoch, 2005</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: stringout Records</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: stringout Records</h1>
|
||||
|
||||
<h2>Normal Operation</h2>
|
||||
@ -62,6 +63,5 @@ written or read value.
|
||||
<a href="scalcout.html">scalcout</a>
|
||||
</p>
|
||||
<p><small>Dirk Zimoch, 2005</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: Tips and Tricks</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: Tips and Tricks</h1>
|
||||
|
||||
<a name="argvar"></a>
|
||||
@ -54,7 +55,7 @@ read {in "new value = %f";}
|
||||
<code>
|
||||
record (ai, "$(RECORD)") {<br>
|
||||
field (DTYP, "stream")<br>
|
||||
field (INP, "@(DEVICETYPE).proto read $(BUS)")<br>
|
||||
field (INP, "@$(DEVICETYPE).proto read $(BUS)")<br>
|
||||
field (SCAN, "I/O Intr")<br>
|
||||
}
|
||||
</code>
|
||||
@ -95,14 +96,14 @@ array_out {separator=", "; out "an array: (%.2f)";}
|
||||
The format <code>%.2f</code> is repeated for each element of the array.
|
||||
All elements are separated by <code>", "</code>.<br>
|
||||
Output will look like this:
|
||||
</p>
|
||||
<pre>
|
||||
an array: (3.14, 17.30, -12.34)
|
||||
</pre>
|
||||
</p>
|
||||
<h3>B) We have up to 12 numeric values</h3>
|
||||
<p>
|
||||
Use a <a href="calcout.html">calcout</a> record and
|
||||
<a href="formats.html#types">field references</a> in the format.
|
||||
<a href="formats.html#redirection">redirection to fields</a>.
|
||||
</p>
|
||||
<p>
|
||||
<code>
|
||||
@ -121,24 +122,24 @@ record (calcout, "$(RECORD)") {<br>
|
||||
field (INPC, "$(C_RECORD)")<br>
|
||||
field (CALC, "0")<br>
|
||||
field (DTYP, "stream")<br>
|
||||
field (OUT, "@(DEVICETYPE).proto write_ABC $(BUS)")<br>
|
||||
field (OUT, "@$(DEVICETYPE).proto write_ABC $(BUS)")<br>
|
||||
}
|
||||
</code>
|
||||
</p>
|
||||
<h3>C) Values are in other records on the same IOC</h3>
|
||||
<p>
|
||||
Use <a href="formats.html#types">record references</a> in the format.
|
||||
Use <a href="formats.html#redirection">redirection to records</a>.
|
||||
</p>
|
||||
<p>
|
||||
<code>
|
||||
acquire {out 'ACQUIRE "%(\$1:directory.VAL)s/%s",%(\$1:time.VAL).3f;';}
|
||||
acquire {out 'ACQUIRE "%(\$1:directory)s/%s",%(\$1:time).3f;';}
|
||||
</code>
|
||||
</p>
|
||||
<p>
|
||||
You must specify the full <code>record.FIELD</code> name,
|
||||
even for <code>VAL</code> fields.
|
||||
To avoid record names in protocol files use
|
||||
<a href="protocol.html#argvar">protocol arguments</a>.
|
||||
You can specify a record name or record.FIELD in parentheses directly
|
||||
after the <code>%</code>.
|
||||
To avoid plain record names in protocol files use
|
||||
<a href="protocol.html#argvar">protocol arguments</a> like <code>\$1</code>.
|
||||
In the link, specify the record name or just the basename of the
|
||||
other records (device name) in parentheses.
|
||||
</p>
|
||||
@ -146,7 +147,7 @@ other records (device name) in parentheses.
|
||||
<code>
|
||||
record (stringout, "$(DEVICE):getimage") {<br>
|
||||
field (DTYP, "stream")<br>
|
||||
field (OUT, "@(DEVICETYPE).proto acquire($(DEVICE)) $(BUS)")<br>
|
||||
field (OUT, "@$(DEVICETYPE).proto acquire($(DEVICE)) $(BUS)")<br>
|
||||
}
|
||||
</code>
|
||||
</p>
|
||||
@ -190,12 +191,12 @@ read_B {in "A=%*f, B=%f";}
|
||||
<code>
|
||||
record (ai, "$(DEVICE):A") {<br>
|
||||
field (DTYP, "stream")<br>
|
||||
field (INP, "@(DEVICETYPE).proto read_A $(BUS)")<br>
|
||||
field (INP, "@$(DEVICETYPE).proto read_A $(BUS)")<br>
|
||||
field (SCAN, "1 second")<br>
|
||||
}<br>
|
||||
record (ai, "$(DEVICE):B") {<br>
|
||||
field (DTYP, "stream")<br>
|
||||
field (INP, "@(DEVICETYPE).proto read_B $(BUS)")<br>
|
||||
field (INP, "@$(DEVICETYPE).proto read_B $(BUS)")<br>
|
||||
field (SCAN, "I/O Intr")<br>
|
||||
}
|
||||
</code>
|
||||
@ -215,20 +216,20 @@ Any non-matching input is ignored by record B.
|
||||
</p>
|
||||
<h3>C) Values should be stored in other records on the same IOC</h3>
|
||||
<p>
|
||||
Use <a href="formats.html#types">record references</a> in the format.
|
||||
Use <a href="formats.html#redirection">redirection to records</a>.
|
||||
To avoid record names in protocol files, use
|
||||
<a href="protocol.html#argvar">protocol arguments</a>.
|
||||
</p>
|
||||
<p>
|
||||
<code>
|
||||
read_AB {out "GET A,B"; in "A=%f, B=%(\$1.VAL)f";}
|
||||
read_AB {out "GET A,B"; in "A=%f, B=%(\$1)f";}
|
||||
</code>
|
||||
</p>
|
||||
<p>
|
||||
<code>
|
||||
record (ai, "$(DEVICE):A") {<br>
|
||||
field (DTYP, "stream")<br>
|
||||
field (INP, "@(DEVICETYPE).proto read_AB($(DEVICE):B) $(BUS)")<br>
|
||||
field (INP, "@$(DEVICETYPE).proto read_AB($(DEVICE):B) $(BUS)")<br>
|
||||
field (SCAN, "1 second")<br>
|
||||
}<br>
|
||||
record (ai, "$(DEVICE):B") {<br>
|
||||
@ -243,11 +244,11 @@ processes record B.
|
||||
</p>
|
||||
|
||||
<a name="mixed"></a>
|
||||
<h2>I have a device that sends mixed data types: numbers and strings</h2>
|
||||
<h2>I have a device that sends mixed data types: numbers or strings</h2>
|
||||
<p>
|
||||
Use a <code>@mismatch</code>
|
||||
<a href="protocol.html#except">exception handler</a> and
|
||||
<a href="formats.html#types">record references</a> in the format.
|
||||
<a href="formats.html#redirection">redirection to records</a>.
|
||||
To avoid record names in protocol files, use
|
||||
<a href="protocol.html#argvar">protocol arguments</a>.
|
||||
</p>
|
||||
@ -259,14 +260,14 @@ When asked "<code>CURRENT?</code>", the device send something like
|
||||
</p>
|
||||
<p>
|
||||
<code>
|
||||
read_current {out "CURRENT?"; in "CURRENT %f A"; @mismatch {in "%(\1.VAL)39c";}}
|
||||
read_current {out "CURRENT?"; in "CURRENT %f A"; @mismatch {in "%(\1)39c";}}
|
||||
</code>
|
||||
</p>
|
||||
<p>
|
||||
<code>
|
||||
record (ai, "$(DEVICE):readcurrent") {<br>
|
||||
field (DTYP, "stream")<br>
|
||||
field (INP, "@(DEVICETYPE).proto read_current($(DEVICE):message) $(BUS)")<br>
|
||||
field (INP, "@$(DEVICETYPE).proto read_current($(DEVICE):message) $(BUS)")<br>
|
||||
}<br>
|
||||
record (stringin, "$(DEVICE):message") {<br>
|
||||
}
|
||||
@ -288,10 +289,124 @@ record (stringout, "$(DEVICE):clean_2") {<br>
|
||||
field (VAL, "OK")<br>
|
||||
field (OUT, "$(DEVICE):message PP")<br>
|
||||
}<br>
|
||||
|
||||
</code>
|
||||
<a name="web"></a>
|
||||
<h2>I need to read a web page</h2>
|
||||
<p>
|
||||
First you have to send a correctly formatted HTML request.
|
||||
Note that this request must contain the full URL like
|
||||
"http://server/page" and must be terminated with <u>two</u> newlines.
|
||||
The server should be the same as in the
|
||||
<a href="setup.html#sta"><code>drvAsynIPPortConfigure</code></a>
|
||||
command (if not using a http proxy).
|
||||
|
||||
The web page you get often contains much more information than you need.
|
||||
<a href="formats.html#regex">Regular expressions</a> are great
|
||||
to find what you are looking for.
|
||||
</p>
|
||||
<h3>Example 1</h3>
|
||||
<p>
|
||||
Read the title of a web page.
|
||||
</p>
|
||||
<p>
|
||||
<code>
|
||||
get_title {<br>
|
||||
extrainput = ignore;<br>
|
||||
replyTimeout = 1000;<br>
|
||||
out "GET http://\$1\n\n";<br>
|
||||
in "%+.1/(?im)<title>(.*)<\/title>/";<br>
|
||||
}
|
||||
</code>
|
||||
</p>
|
||||
<p>
|
||||
Terminate the request with two newlines, either explicit like here
|
||||
<u>or</u> using an
|
||||
<a href="protocol.html#sysvar"><code>outTerminator</code></a>.
|
||||
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>.
|
||||
Note that web servers may be slow, so allow some
|
||||
<a href="protocol.html#argvar"><code>replyTimeout</code></a>.
|
||||
</p>
|
||||
<p>
|
||||
If you don't use an <code>inTerminator</code> then the whole page is
|
||||
read as one "line" to the <code>in</code> command and can be parsed easily
|
||||
with a regular expression.
|
||||
We want to see the string between <code><title></code> and
|
||||
<code></title></code>, so we put it into a subexpression in
|
||||
<code>()</code> and request the first subexpression with <code>.1</code>.
|
||||
Note that the <code>/</code> in the closing tag has be be escaped
|
||||
to avoid a misinterpretation as the closing <code>/</code> of the regular
|
||||
expression.
|
||||
</p>
|
||||
<p>
|
||||
The tags may be upper or lower case like <code><TITLE></code> or
|
||||
<code><Title></code>, so we ask for case insensitive matching with
|
||||
<code>(?i)</code>.
|
||||
</p>
|
||||
<p>
|
||||
The string should be terminated with the first closing
|
||||
<code></title></code>, not the last one in the file.
|
||||
(There should not be more than one title but you never know.)
|
||||
Thus we ask not to be greedy with <code>(?m)</code>.
|
||||
<code>(?i)</code> and <code>(?m)</code> can be combined to <code>(?im)</code>.
|
||||
See the PCRE documentation for more regexp syntax.
|
||||
</p>
|
||||
<p>
|
||||
The regular expression matcher ignores and discards any content before the
|
||||
matching section.
|
||||
Content after the match is discarded with <code>extrainput = ignore</code>
|
||||
so that it does not trigger errors reporting "surplus input".
|
||||
</p>
|
||||
<p>
|
||||
Finally, the title may be too long for the record.
|
||||
The <code>+</code> tells the format matcher not to fail in this case
|
||||
but to truncate the string instead.
|
||||
You can read the string with a stringin record or for longer strings with
|
||||
a waveform record with data type CHAR.
|
||||
</p>
|
||||
<p>
|
||||
<code>
|
||||
record (stringin, "$(DEVICE):title") {<br>
|
||||
field (DTYP, "stream")<br>
|
||||
field (INP, "@$(DEVICETYPE).proto get_title($(PAGE)) $(BUS)")<br>
|
||||
}<br>
|
||||
record (waveform, "$(DEVICE):longtitle") {<br>
|
||||
field (DTYP, "stream")<br>
|
||||
field (INP, "@$(DEVICETYPE).proto get_title($(PAGE)) $(BUS)")<br>
|
||||
field (FTVL, "CHAR")<br>
|
||||
field (NELM, "100")<br>
|
||||
}<br>
|
||||
</code>
|
||||
</p>
|
||||
|
||||
<h3>Example 2</h3>
|
||||
<p>
|
||||
Read a number from a web page. First we have to locate the number.
|
||||
For that we match against any known string right before the number
|
||||
(and <a href="formats.html#syntax">discard the match</a> with <code>*</code>).
|
||||
Then we read the number.
|
||||
</p>
|
||||
<code>
|
||||
get_title {<br>
|
||||
extrainput = ignore;<br>
|
||||
replyTimeout = 1000;<br>
|
||||
out "GET http://\$1\n\n";<br>
|
||||
in "%*/Interesting value:/%f more text";<br>
|
||||
}
|
||||
</code>
|
||||
<p>
|
||||
When using <code>extrainput = ignore;</code>, it is always a good idea to
|
||||
match a few bytes after the value, too.
|
||||
This catches errors where loading of the page is interrupted in the middle
|
||||
of the number. (You don't want to miss the exponent from something like 1.23E-14).
|
||||
</p>
|
||||
<p>
|
||||
You can read more than one value from a file with successive regular expressions
|
||||
and <a href="formats.html#redirection">redirections</a>.
|
||||
But this only works if the order of the values is predictible.
|
||||
<i>StreamDevice</i> is not an XML parser! It always reads sequentially.
|
||||
</p>
|
||||
<hr>
|
||||
<p><small>Dirk Zimoch, 2007</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
<p><small>Dirk Zimoch, 2012</small></p>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,12 +3,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>StreamDevice: waveform Records</title>
|
||||
<link rel="shortcut icon" href="sls_icon.ico">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="stream.css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<meta name="author" content="Dirk Zimoch">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="nav.html" id="navleft"></iframe>
|
||||
<h1>StreamDevice: waveform Records</h1>
|
||||
|
||||
<h2>Normal Operation</h2>
|
||||
@ -121,6 +122,5 @@ the written or read value.
|
||||
<a href="scalcout.html">scalcout</a>
|
||||
</p>
|
||||
<p><small>Dirk Zimoch, 2005</small></p>
|
||||
<script src="stream.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
58
makefile
Normal file
@ -0,0 +1,58 @@
|
||||
include /ioc/tools/driver.makefile
|
||||
EXCLUDE_VERSIONS = 3.13.2
|
||||
PROJECT=stream2
|
||||
BUILDCLASSES += Linux
|
||||
|
||||
#DOCUDIR = doc
|
||||
|
||||
DBDS = stream.dbd
|
||||
|
||||
BUSSES += AsynDriver
|
||||
BUSSES += Dummy
|
||||
|
||||
FORMATS += Enum
|
||||
FORMATS += BCD
|
||||
FORMATS += Raw
|
||||
FORMATS += RawFloat
|
||||
FORMATS += Binary
|
||||
FORMATS += Checksum
|
||||
FORMATS += Regexp
|
||||
FORMATS += MantissaExponent
|
||||
FORMATS += Timestamp
|
||||
|
||||
RECORDTYPES += aai aao
|
||||
RECORDTYPES += ao ai
|
||||
RECORDTYPES += bo bi
|
||||
RECORDTYPES += mbbo mbbi
|
||||
RECORDTYPES += mbboDirect mbbiDirect
|
||||
RECORDTYPES += longout longin
|
||||
RECORDTYPES += stringout stringin
|
||||
RECORDTYPES += waveform
|
||||
|
||||
SOURCES += $(RECORDTYPES:%=src/dev%Stream.c)
|
||||
SOURCES += $(FORMATS:%=src/%Converter.cc)
|
||||
SOURCES += $(BUSSES:%=src/%Interface.cc)
|
||||
SOURCES += $(wildcard src/Stream*.cc)
|
||||
SOURCES += src/StreamVersion.c
|
||||
|
||||
HEADERS += devStream.h
|
||||
HEADERS += StreamFormat.h
|
||||
HEADERS += StreamFormatConverter.h
|
||||
HEADERS += StreamBuffer.h
|
||||
HEADERS += StreamError.h
|
||||
|
||||
ifeq (${EPICS_BASETYPE},3.13)
|
||||
USR_INCLUDES += -include $(INSTALL_INCLUDE)/compat3_13.h
|
||||
endif
|
||||
ifeq (${EPICS_BASETYPE},3.14)
|
||||
RECORDTYPES += calcout
|
||||
endif
|
||||
|
||||
StreamCore.o: streamReferences
|
||||
|
||||
streamReferences:
|
||||
perl ../src/makeref.pl Interface $(BUSSES) > $@
|
||||
perl ../src/makeref.pl Converter $(FORMATS) >> $@
|
||||
|
||||
stream.dbd:
|
||||
perl ../src/makedbd.pl $(RECORDTYPES) > $@
|
@ -22,14 +22,19 @@
|
||||
#include "StreamError.h"
|
||||
#include "StreamBuffer.h"
|
||||
|
||||
#ifdef EPICS_3_14
|
||||
#include <epicsAssert.h>
|
||||
#include <epicsTime.h>
|
||||
#include <epicsTimer.h>
|
||||
#else
|
||||
#include <epicsVersion.h>
|
||||
#ifdef BASE_VERSION
|
||||
#define EPICS_3_13
|
||||
#endif
|
||||
|
||||
#ifdef EPICS_3_13
|
||||
#include <assert.h>
|
||||
#include <wdLib.h>
|
||||
#include <sysLib.h>
|
||||
#else
|
||||
#include <epicsAssert.h>
|
||||
#include <epicsTime.h>
|
||||
#include <epicsTimer.h>
|
||||
extern "C" {
|
||||
#include <callback.h>
|
||||
}
|
||||
@ -59,12 +64,12 @@ writeRequest()
|
||||
pasynManager->queueRequest()
|
||||
when request is handled
|
||||
pasynOctet->flush()
|
||||
pasynOctet->writeRaw()
|
||||
if writeRaw() times out
|
||||
pasynOctet->write()
|
||||
if write() times out
|
||||
writeCallback(StreamIoTimeout)
|
||||
if writeRaw fails otherwise
|
||||
if write fails otherwise
|
||||
writeCallback(StreamIoFault)
|
||||
if writeRaw succeeds and all bytes have been written
|
||||
if write succeeds and all bytes have been written
|
||||
writeCallback(StreamIoSuccess)
|
||||
if not all bytes can be written
|
||||
pasynManager->queueRequest() to write next part
|
||||
@ -126,7 +131,7 @@ static const char* ioActionStr[] = {
|
||||
};
|
||||
|
||||
static const char* asynStatusStr[] = {
|
||||
"asynSuccess", "asynTimeout", "asynOverflow", "asynError"
|
||||
"asynSuccess", "asynTimeout", "asynOverflow", "asynError", "asynDisconnected", "asynDisabled"
|
||||
};
|
||||
|
||||
static const char* eomReasonStr[] = {
|
||||
@ -134,7 +139,7 @@ static const char* eomReasonStr[] = {
|
||||
};
|
||||
|
||||
class AsynDriverInterface : StreamBusInterface
|
||||
#ifdef EPICS_3_14
|
||||
#ifndef EPICS_3_13
|
||||
, epicsTimerNotify
|
||||
#endif
|
||||
{
|
||||
@ -164,12 +169,12 @@ class AsynDriverInterface : StreamBusInterface
|
||||
const char* outputBuffer;
|
||||
size_t outputSize;
|
||||
int peeksize;
|
||||
#ifdef EPICS_3_14
|
||||
epicsTimerQueueActive* timerQueue;
|
||||
epicsTimer* timer;
|
||||
#else
|
||||
#ifdef EPICS_3_13
|
||||
WDOG_ID timer;
|
||||
CALLBACK timeoutCallback;
|
||||
#else
|
||||
epicsTimerQueueActive* timerQueue;
|
||||
epicsTimer* timer;
|
||||
#endif
|
||||
|
||||
AsynDriverInterface(Client* client);
|
||||
@ -189,11 +194,11 @@ class AsynDriverInterface : StreamBusInterface
|
||||
bool disconnectRequest();
|
||||
void finish();
|
||||
|
||||
#ifdef EPICS_3_14
|
||||
#ifdef EPICS_3_13
|
||||
static void expire(CALLBACK *pcallback);
|
||||
#else
|
||||
// epicsTimerNotify methods
|
||||
epicsTimerNotify::expireStatus expire(const epicsTime &);
|
||||
#else
|
||||
static void expire(CALLBACK *pcallback);
|
||||
#endif
|
||||
|
||||
// local methods
|
||||
@ -205,26 +210,28 @@ class AsynDriverInterface : StreamBusInterface
|
||||
void connectHandler();
|
||||
void disconnectHandler();
|
||||
bool connectToAsynPort();
|
||||
void asynReadHandler(const char *data, int numchars, int eomReason);
|
||||
void asynReadHandler(const char *data, size_t numchars, int eomReason);
|
||||
asynQueuePriority priority() {
|
||||
return static_cast<asynQueuePriority>
|
||||
(StreamBusInterface::priority());
|
||||
}
|
||||
void startTimer(double timeout) {
|
||||
#ifdef EPICS_3_14
|
||||
timer->start(*this, timeout);
|
||||
#else
|
||||
#ifdef EPICS_3_13
|
||||
callbackSetPriority(priority(), &timeoutCallback);
|
||||
wdStart(timer, (int)((timeout+1)*sysClkRateGet())-1,
|
||||
reinterpret_cast<FUNCPTR>(callbackRequest),
|
||||
reinterpret_cast<int>(&timeoutCallback));
|
||||
#else
|
||||
timer->start(*this, timeout
|
||||
+epicsThreadSleepQuantum()*0.5
|
||||
);
|
||||
#endif
|
||||
}
|
||||
void cancelTimer() {
|
||||
#ifdef EPICS_3_14
|
||||
timer->cancel();
|
||||
#else
|
||||
#ifdef EPICS_3_13
|
||||
wdCancel(timer);
|
||||
#else
|
||||
timer->cancel();
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -263,15 +270,15 @@ AsynDriverInterface(Client* client) : StreamBusInterface(client)
|
||||
handleTimeout);
|
||||
assert(pasynUser);
|
||||
pasynUser->userPvt = this;
|
||||
#ifdef EPICS_3_14
|
||||
#ifdef EPICS_3_13
|
||||
timer = wdCreate();
|
||||
callbackSetCallback(expire, &timeoutCallback);
|
||||
callbackSetUser(this, &timeoutCallback);
|
||||
#else
|
||||
timerQueue = &epicsTimerQueueActive::allocate(true);
|
||||
assert(timerQueue);
|
||||
timer = &timerQueue->createTimer();
|
||||
assert(timer);
|
||||
#else
|
||||
timer = wdCreate();
|
||||
callbackSetCallback(expire, &timeoutCallback);
|
||||
callbackSetUser(this, &timeoutCallback);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -306,11 +313,11 @@ AsynDriverInterface::
|
||||
}
|
||||
// Now, no handler is running any more and none will start.
|
||||
|
||||
#ifdef EPICS_3_14
|
||||
#ifdef EPICS_3_13
|
||||
wdDelete(timer);
|
||||
#else
|
||||
timer->destroy();
|
||||
timerQueue->release();
|
||||
#else
|
||||
wdDelete(timer);
|
||||
#endif
|
||||
pasynManager->disconnect(pasynUser);
|
||||
pasynManager->freeAsynUser(pasynUser);
|
||||
@ -458,12 +465,22 @@ connectToBus(const char* busname, int addr)
|
||||
bool AsynDriverInterface::
|
||||
lockRequest(unsigned long lockTimeout_ms)
|
||||
{
|
||||
int connected;
|
||||
asynStatus status;
|
||||
|
||||
debug("AsynDriverInterface::lockRequest(%s, %ld msec)\n",
|
||||
clientName(), lockTimeout_ms);
|
||||
asynStatus status;
|
||||
lockTimeout = lockTimeout_ms ? lockTimeout_ms*0.001 : -1.0;
|
||||
ioAction = Lock;
|
||||
status = pasynManager->queueRequest(pasynUser, priority(),
|
||||
status = pasynManager->isConnected(pasynUser, &connected);
|
||||
if (status != asynSuccess)
|
||||
{
|
||||
error("%s: pasynManager->isConnected() failed: %s\n",
|
||||
clientName(), pasynUser->errorMessage);
|
||||
return false;
|
||||
}
|
||||
status = pasynManager->queueRequest(pasynUser,
|
||||
connected ? priority() : asynQueuePriorityConnect,
|
||||
lockTimeout);
|
||||
if (status != asynSuccess)
|
||||
{
|
||||
@ -492,6 +509,30 @@ connectToAsynPort()
|
||||
clientName(), pasynUser->errorMessage);
|
||||
return false;
|
||||
}
|
||||
// Is it really connected?
|
||||
|
||||
/* does not work because read(...,0,...) deletes 1 byte from input.
|
||||
|
||||
if (connected && !pasynGpib)
|
||||
{
|
||||
size_t received;
|
||||
int eomReason;
|
||||
char buffer[8];
|
||||
|
||||
pasynUser->timeout = 0.0;
|
||||
status = pasynOctet->read(pvtOctet, pasynUser,
|
||||
buffer, 0, &received, &eomReason);
|
||||
debug("AsynDriverInterface::connectToAsynPort(%s): "
|
||||
"read(..., 0, ...) [timeout=%g sec] = %s\n",
|
||||
clientName(), pasynUser->timeout,
|
||||
asynStatusStr[status]);
|
||||
pasynManager->isConnected(pasynUser, &connected);
|
||||
debug("AsynDriverInterface::connectToAsynPort(%s): "
|
||||
"device was %sconnected!\n",
|
||||
clientName(),connected?"":"dis");
|
||||
}
|
||||
*/
|
||||
|
||||
debug("AsynDriverInterface::connectToAsynPort(%s) is %s connected\n",
|
||||
clientName(), connected ? "already" : "not yet");
|
||||
if (!connected)
|
||||
@ -576,34 +617,72 @@ writeHandler()
|
||||
clientName());
|
||||
asynStatus status;
|
||||
size_t written = 0;
|
||||
pasynUser->timeout = writeTimeout;
|
||||
|
||||
// discard any early input or early events
|
||||
status = pasynOctet->flush(pvtOctet, pasynUser);
|
||||
// discard any early input, but forward it to potential async records
|
||||
// thus do not use pasynOctet->flush()
|
||||
pasynUser->timeout = 0;
|
||||
do {
|
||||
char buffer [256];
|
||||
size_t received = sizeof(buffer);
|
||||
int eomReason = 0;
|
||||
status = pasynOctet->read(pvtOctet, pasynUser,
|
||||
buffer, received, &received, &eomReason);
|
||||
#ifndef NO_TEMPORARY
|
||||
if (received) debug("AsynDriverInterface::writeHandler(%s): flushing %ld bytes: \"%s\"\n",
|
||||
clientName(), (long)received, StreamBuffer(buffer, received).expand()());
|
||||
#endif
|
||||
} while (status != asynTimeout);
|
||||
|
||||
// discard any early events
|
||||
receivedEvent = 0;
|
||||
|
||||
if (status != asynSuccess)
|
||||
{
|
||||
error("%s: pasynOctet->flush() failed: %s\n",
|
||||
clientName(), pasynUser->errorMessage);
|
||||
writeCallback(StreamIoFault);
|
||||
return;
|
||||
}
|
||||
|
||||
pasynUser->timeout = writeTimeout;
|
||||
|
||||
// has stream already added a terminator or should
|
||||
// asyn do so?
|
||||
|
||||
size_t streameoslen;
|
||||
const char* streameos = getOutTerminator(streameoslen);
|
||||
if (streameos) // stream has added eos
|
||||
int oldeoslen = -1;
|
||||
char oldeos[16];
|
||||
if (streameos) // stream has already added eos, don't do it again in asyn
|
||||
{
|
||||
status = pasynOctet->writeRaw(pvtOctet, pasynUser,
|
||||
outputBuffer, outputSize, &written);
|
||||
// clear terminator for asyn
|
||||
status = pasynOctet->getOutputEos(pvtOctet,
|
||||
pasynUser, oldeos, sizeof(oldeos)-1, &oldeoslen);
|
||||
if (status != asynSuccess)
|
||||
{
|
||||
oldeoslen = -1;
|
||||
// No EOS support?
|
||||
}
|
||||
pasynOctet->setOutputEos(pvtOctet, pasynUser,
|
||||
NULL, 0);
|
||||
}
|
||||
else // asyn should add eos
|
||||
status = pasynOctet->write(pvtOctet, pasynUser,
|
||||
outputBuffer, outputSize, &written);
|
||||
debug("AsynDriverInterface::writeHandler(%s): "
|
||||
"write(..., outputSize=%ld, written=%ld) "
|
||||
"[timeout=%g sec] = %s\n",
|
||||
clientName(), (long)outputSize, (long)written,
|
||||
pasynUser->timeout, asynStatusStr[status]);
|
||||
|
||||
// Up to asyn 4.17 I can't see when the server has disconnected. Why?
|
||||
int connected;
|
||||
pasynManager->isConnected(pasynUser, &connected);
|
||||
debug("AsynDriverInterface::writeHandler(%s): "
|
||||
"device is %sconnected\n",
|
||||
clientName(),connected?"":"dis");
|
||||
if (!connected) {
|
||||
error("%s: connection closed in write\n",
|
||||
clientName());
|
||||
writeCallback(StreamIoFault);
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldeoslen >= 0) // restore asyn terminator
|
||||
{
|
||||
status = pasynOctet->write(pvtOctet, pasynUser,
|
||||
outputBuffer, outputSize, &written);
|
||||
pasynOctet->setOutputEos(pvtOctet, pasynUser,
|
||||
oldeos, oldeoslen);
|
||||
}
|
||||
switch (status)
|
||||
{
|
||||
@ -641,6 +720,23 @@ writeHandler()
|
||||
clientName(), pasynUser->errorMessage);
|
||||
writeCallback(StreamIoFault);
|
||||
return;
|
||||
#ifdef ASYN_VERSION // asyn >= 4.14
|
||||
case asynDisconnected:
|
||||
error("%s: asynDisconnected in write: %s\n",
|
||||
clientName(), pasynUser->errorMessage);
|
||||
writeCallback(StreamIoFault);
|
||||
return;
|
||||
case asynDisabled:
|
||||
error("%s: asynDisconnected in write: %s\n",
|
||||
clientName(), pasynUser->errorMessage);
|
||||
writeCallback(StreamIoFault);
|
||||
return;
|
||||
#endif
|
||||
default:
|
||||
error("%s: unknown asyn error in write: %s\n",
|
||||
clientName(), pasynUser->errorMessage);
|
||||
writeCallback(StreamIoFault);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -650,7 +746,7 @@ readRequest(unsigned long replyTimeout_ms, unsigned long readTimeout_ms,
|
||||
long _expectedLength, bool async)
|
||||
{
|
||||
debug("AsynDriverInterface::readRequest(%s, %ld msec reply, "
|
||||
"%ld msec read, expect %ld bytes, asyn=%s)\n",
|
||||
"%ld msec read, expect %ld bytes, async=%s)\n",
|
||||
clientName(), replyTimeout_ms, readTimeout_ms,
|
||||
_expectedLength, async?"yes":"no");
|
||||
|
||||
@ -674,8 +770,20 @@ readRequest(unsigned long replyTimeout_ms, unsigned long readTimeout_ms,
|
||||
}
|
||||
status = pasynManager->queueRequest(pasynUser,
|
||||
priority(), queueTimeout);
|
||||
if (status != asynSuccess && !async)
|
||||
debug("AsynDriverInterface::readRequest %s: "
|
||||
"queueRequest(..., priority=%d, queueTimeout=%g sec) = %s [async=%s] %s\n",
|
||||
clientName(), priority(), queueTimeout,
|
||||
asynStatusStr[status], async? "true" : "false",
|
||||
status!=asynSuccess ? pasynUser->errorMessage : "");
|
||||
if (status != asynSuccess)
|
||||
{
|
||||
// Not queued for some reason (e.g. disconnected / already queued)
|
||||
if (async)
|
||||
{
|
||||
// silently try again later
|
||||
startTimer(replyTimeout);
|
||||
return true;
|
||||
}
|
||||
error("%s readRequest: pasynManager->queueRequest() failed: %s\n",
|
||||
clientName(), pasynUser->errorMessage);
|
||||
return false;
|
||||
@ -700,40 +808,46 @@ readHandler()
|
||||
streameos = getInTerminator(streameoslen);
|
||||
deveos = streameos;
|
||||
deveoslen = streameoslen;
|
||||
if (streameos) do // streameos == NULL means: don't change eos
|
||||
if (streameos) // streameos == NULL means: don't change eos
|
||||
{
|
||||
asynStatus status;
|
||||
status = pasynOctet->getInputEos(pvtOctet,
|
||||
pasynUser, oldeos, sizeof(oldeos)-1, &oldeoslen);
|
||||
if (status != asynSuccess)
|
||||
{
|
||||
oldeoslen = -1;
|
||||
// No EOS support?
|
||||
}
|
||||
// device (e.g. GPIB) might not accept full eos length
|
||||
if (pasynOctet->setInputEos(pvtOctet, pasynUser,
|
||||
deveos, deveoslen) == asynSuccess)
|
||||
{
|
||||
#ifndef NO_TEMPORARY
|
||||
if (ioAction != AsyncRead)
|
||||
if (streameos[0])
|
||||
{
|
||||
debug("AsynDriverInterface::readHandler(%s) "
|
||||
"input EOS set to %s\n",
|
||||
clientName(),
|
||||
StreamBuffer(deveos, deveoslen).expand()());
|
||||
error("%s: warning: No input EOS support.\n",
|
||||
clientName());
|
||||
}
|
||||
oldeoslen = -1;
|
||||
} else do {
|
||||
// device (e.g. GPIB) might not accept full eos length
|
||||
if (pasynOctet->setInputEos(pvtOctet, pasynUser,
|
||||
deveos, deveoslen) == asynSuccess)
|
||||
{
|
||||
#ifndef NO_TEMPORARY
|
||||
if (ioAction != AsyncRead)
|
||||
{
|
||||
debug("AsynDriverInterface::readHandler(%s) "
|
||||
"input EOS set to %s\n",
|
||||
clientName(),
|
||||
StreamBuffer(deveos, deveoslen).expand()());
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
deveos++; deveoslen--;
|
||||
if (!deveoslen)
|
||||
{
|
||||
error("%s: warning: pasynOctet->setInputEos() failed: %s\n",
|
||||
clientName(), pasynUser->errorMessage);
|
||||
}
|
||||
} while (deveoslen);
|
||||
break;
|
||||
}
|
||||
deveos++; deveoslen--;
|
||||
if (!deveoslen)
|
||||
{
|
||||
error("%s: warning: pasynOctet->setInputEos() failed: %s\n",
|
||||
clientName(), pasynUser->errorMessage);
|
||||
}
|
||||
} while (deveoslen);
|
||||
}
|
||||
|
||||
int bytesToRead = peeksize;
|
||||
long bytesToRead = peeksize;
|
||||
long buffersize;
|
||||
|
||||
if (expectedLength > 0)
|
||||
@ -755,7 +869,7 @@ readHandler()
|
||||
{
|
||||
// In AsyncRead mode just poll
|
||||
// and read as much as possible
|
||||
pasynUser->timeout = readTimeout;
|
||||
pasynUser->timeout = 0.0;
|
||||
bytesToRead = buffersize;
|
||||
}
|
||||
else
|
||||
@ -763,10 +877,11 @@ readHandler()
|
||||
pasynUser->timeout = replyTimeout;
|
||||
}
|
||||
bool waitForReply = true;
|
||||
int received;
|
||||
size_t received;
|
||||
int eomReason;
|
||||
asynStatus status;
|
||||
long readMore;
|
||||
int connected;
|
||||
|
||||
while (1)
|
||||
{
|
||||
@ -774,41 +889,62 @@ readHandler()
|
||||
received = 0;
|
||||
eomReason = 0;
|
||||
|
||||
debug("AsynDriverInterface::readHandler(%s): ioAction=%s "
|
||||
"read(..., bytesToRead=%ld, ...) "
|
||||
"[timeout=%g sec]\n",
|
||||
clientName(), ioActionStr[ioAction],
|
||||
bytesToRead, pasynUser->timeout);
|
||||
status = pasynOctet->read(pvtOctet, pasynUser,
|
||||
buffer, bytesToRead, (size_t*)&received, &eomReason);
|
||||
if (ioAction != AsyncRead || status != asynTimeout)
|
||||
buffer, bytesToRead, &received, &eomReason);
|
||||
debug("AsynDriverInterface::readHandler(%s): "
|
||||
"read returned %s: ioAction=%s received=%ld, eomReason=%s, buffer=\"%s\"\n",
|
||||
clientName(), asynStatusStr[status], ioActionStr[ioAction],
|
||||
(long)received,eomReasonStr[eomReason&0x7],
|
||||
StreamBuffer(buffer, received).expand()());
|
||||
|
||||
pasynManager->isConnected(pasynUser, &connected);
|
||||
debug("AsynDriverInterface::readHandler(%s): "
|
||||
"device is now %sconnected\n",
|
||||
clientName(),connected?"":"dis");
|
||||
// asyn 4.16 sets reason to ASYN_EOM_END when device disconnects.
|
||||
// What about earlier versions?
|
||||
if (!connected) eomReason |= ASYN_EOM_END;
|
||||
|
||||
if (status == asynTimeout &&
|
||||
pasynUser->timeout == 0.0 &&
|
||||
received > 0)
|
||||
{
|
||||
debug("AsynDriverInterface::readHandler(%s): "
|
||||
"read(..., bytesToRead=%d, ...) [timeout=%f seconds] = %s\n",
|
||||
clientName(), bytesToRead, pasynUser->timeout,
|
||||
asynStatusStr[status]);
|
||||
// Jens Eden (PTB) pointed out that polling asynInterposeEos
|
||||
// with timeout = 0.0 returns asynTimeout even when bytes
|
||||
// have been received, but not yet the terminator.
|
||||
status = asynSuccess;
|
||||
}
|
||||
// pasynOctet->read() has already cut off terminator.
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case asynSuccess:
|
||||
if (ioAction == AsyncRead)
|
||||
if (ioAction != Read)
|
||||
{
|
||||
#ifndef NO_TEMPORARY
|
||||
debug("AsynDriverInterface::readHandler(%s): "
|
||||
"AsyncRead poll: received %d of %d bytes \"%s\" "
|
||||
"AsyncRead poll: received %ld of %ld bytes \"%s\" "
|
||||
"eomReason=%s [data ignored]\n",
|
||||
clientName(), received, bytesToRead,
|
||||
clientName(), (long)received, bytesToRead,
|
||||
StreamBuffer(buffer, received).expand()(),
|
||||
eomReasonStr[eomReason&0x7]);
|
||||
#endif
|
||||
// ignore what we got from here.
|
||||
// input was already handeled by asynReadHandler()
|
||||
|
||||
// read until no more input is available
|
||||
readMore = -1;
|
||||
break;
|
||||
}
|
||||
#ifndef NO_TEMPORARY
|
||||
debug("AsynDriverInterface::readHandler(%s): "
|
||||
"received %d of %d bytes \"%s\" "
|
||||
"received %ld of %ld bytes \"%s\" "
|
||||
"eomReason=%s\n",
|
||||
clientName(), received, bytesToRead,
|
||||
clientName(), (long)received, bytesToRead,
|
||||
StreamBuffer(buffer, received).expand()(),
|
||||
eomReasonStr[eomReason&0x7]);
|
||||
#endif
|
||||
@ -829,7 +965,7 @@ readHandler()
|
||||
size_t i;
|
||||
for (i = 0; i < deveoslen; i++, received++)
|
||||
{
|
||||
if (received >= 0) buffer[received] = deveos[i];
|
||||
if ((int)received >= 0) buffer[received] = deveos[i];
|
||||
// It is safe to add to buffer here, because
|
||||
// the terminator was already there before
|
||||
// asynOctet->read() had cut it.
|
||||
@ -849,6 +985,9 @@ readHandler()
|
||||
// reply timeout
|
||||
if (ioAction == AsyncRead)
|
||||
{
|
||||
debug("AsynDriverInterface::readHandler(%s): "
|
||||
"no async input, retry in in %g seconds\n",
|
||||
clientName(), replyTimeout);
|
||||
// start next poll after timer expires
|
||||
if (replyTimeout != 0.0) startTimer(replyTimeout);
|
||||
// continues with:
|
||||
@ -865,9 +1004,10 @@ readHandler()
|
||||
// read timeout
|
||||
#ifndef NO_TEMPORARY
|
||||
debug("AsynDriverInterface::readHandler(%s): "
|
||||
"ioAction=%s, timeout after %d of %d bytes \"%s\"\n",
|
||||
clientName(), ioActionStr[ioAction],
|
||||
received, bytesToRead,
|
||||
"ioAction=%s, timeout [%g sec] "
|
||||
"after %ld of %ld bytes \"%s\"\n",
|
||||
clientName(), ioActionStr[ioAction], pasynUser->timeout,
|
||||
(long)received, bytesToRead,
|
||||
StreamBuffer(buffer, received).expand()());
|
||||
#endif
|
||||
if (ioAction == AsyncRead || ioAction == AsyncReadMore)
|
||||
@ -900,6 +1040,23 @@ readHandler()
|
||||
clientName(), pasynUser->errorMessage);
|
||||
readCallback(StreamIoFault, buffer, received);
|
||||
break;
|
||||
#ifdef ASYN_VERSION // asyn >= 4.14
|
||||
case asynDisconnected:
|
||||
error("%s: asynDisconnected in read: %s\n",
|
||||
clientName(), pasynUser->errorMessage);
|
||||
readCallback(StreamIoFault);
|
||||
return;
|
||||
case asynDisabled:
|
||||
error("%s: asynDisconnected in read: %s\n",
|
||||
clientName(), pasynUser->errorMessage);
|
||||
readCallback(StreamIoFault);
|
||||
return;
|
||||
#endif
|
||||
default:
|
||||
error("%s: unknown asyn error in read: %s\n",
|
||||
clientName(), pasynUser->errorMessage);
|
||||
readCallback(StreamIoFault);
|
||||
return;
|
||||
}
|
||||
if (!readMore) break;
|
||||
if (readMore > 0)
|
||||
@ -911,7 +1068,7 @@ readHandler()
|
||||
bytesToRead = inputBuffer.capacity();
|
||||
}
|
||||
debug("AsynDriverInterface::readHandler(%s) "
|
||||
"readMore=%ld bytesToRead=%d\n",
|
||||
"readMore=%ld bytesToRead=%ld\n",
|
||||
clientName(), readMore, bytesToRead);
|
||||
pasynUser->timeout = readTimeout;
|
||||
waitForReply = false;
|
||||
@ -934,31 +1091,16 @@ void intrCallbackOctet(void* /*pvt*/, asynUser *pasynUser,
|
||||
// Problems here:
|
||||
// 1. We get this message too when we are the poller.
|
||||
// Thus we have to ignore what we got from polling.
|
||||
// 2. We get this message multiple times when original reader
|
||||
// reads in chunks.
|
||||
// 3. eomReason=ASYN_EOM_CNT when message was too long for
|
||||
// 2. eomReason=ASYN_EOM_CNT when message was too long for
|
||||
// internal buffer of asynDriver.
|
||||
|
||||
if (!interruptAccept) return; // too early to process records
|
||||
if (interface->ioAction == AsyncRead ||
|
||||
interface->ioAction == AsyncReadMore)
|
||||
{
|
||||
interface->asynReadHandler(data, numchars, eomReason);
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifndef NO_TEMPORARY
|
||||
debug("AsynDriverInterface::intrCallbackOctet(%s, buffer=\"%s\", "
|
||||
"received=%d eomReason=%s) ioAction=%s\n",
|
||||
interface->clientName(), StreamBuffer(data, numchars).expand()(),
|
||||
numchars, eomReasonStr[eomReason&0x7], ioActionStr[interface->ioAction]);
|
||||
#endif
|
||||
}
|
||||
interface->asynReadHandler(data, numchars, eomReason);
|
||||
}
|
||||
|
||||
// get asynchronous input
|
||||
void AsynDriverInterface::
|
||||
asynReadHandler(const char *buffer, int received, int eomReason)
|
||||
asynReadHandler(const char *buffer, size_t received, int eomReason)
|
||||
{
|
||||
// Due to multithreading, timerExpired() might come at any time.
|
||||
// It queues the next poll request which is now useless because
|
||||
@ -974,9 +1116,9 @@ asynReadHandler(const char *buffer, int received, int eomReason)
|
||||
|
||||
#ifndef NO_TEMPORARY
|
||||
debug("AsynDriverInterface::asynReadHandler(%s, buffer=\"%s\", "
|
||||
"received=%d eomReason=%s) ioAction=%s\n",
|
||||
"received=%ld eomReason=%s) ioAction=%s\n",
|
||||
clientName(), StreamBuffer(buffer, received).expand()(),
|
||||
received, eomReasonStr[eomReason&0x7], ioActionStr[ioAction]);
|
||||
(long)received, eomReasonStr[eomReason&0x7], ioActionStr[ioAction]);
|
||||
#endif
|
||||
|
||||
ioAction = None;
|
||||
@ -1032,7 +1174,7 @@ asynReadHandler(const char *buffer, int received, int eomReason)
|
||||
// set by stream, cut it off now.
|
||||
status = pasynOctet->getInputEos(pvtOctet,
|
||||
pasynUser, deveos, sizeof(deveos)-1, &deveoslen);
|
||||
if (status == asynSuccess && received >= deveoslen)
|
||||
if (status == asynSuccess && (long)received >= (long)deveoslen)
|
||||
{
|
||||
int i;
|
||||
for (i = 1; i <= deveoslen; i++)
|
||||
@ -1123,6 +1265,9 @@ timerExpired()
|
||||
int autoconnect, connected;
|
||||
switch (ioAction)
|
||||
{
|
||||
case None:
|
||||
// Timeout of async poll crossed with parasitic input
|
||||
return;
|
||||
case ReceiveEvent:
|
||||
// timeout while waiting for event
|
||||
ioAction = None;
|
||||
@ -1141,6 +1286,8 @@ timerExpired()
|
||||
// queueRequest might fail if another request was just queued
|
||||
pasynManager->isAutoConnect(pasynUser, &autoconnect);
|
||||
pasynManager->isConnected(pasynUser, &connected);
|
||||
debug("%s: polling for I/O Intr: autoconnected: %d, connect: %d\n",
|
||||
clientName(), autoconnect, connected);
|
||||
if (autoconnect && !connected)
|
||||
{
|
||||
// has explicitely been disconnected
|
||||
@ -1151,8 +1298,14 @@ timerExpired()
|
||||
else
|
||||
{
|
||||
// queue for read poll (no timeout)
|
||||
pasynManager->queueRequest(pasynUser,
|
||||
asynStatus status = pasynManager->queueRequest(pasynUser,
|
||||
asynQueuePriorityLow, -1.0);
|
||||
// if this fails, we are already queued by another thread
|
||||
debug("AsynDriverInterface::timerExpired %s: "
|
||||
"queueRequest(..., priority=Low, queueTimeout=-1) = %s %s\n",
|
||||
clientName(), asynStatusStr[status],
|
||||
status!=asynSuccess ? pasynUser->errorMessage : "");
|
||||
if (status != asynSuccess) startTimer(replyTimeout);
|
||||
// continues with:
|
||||
// handleRequest() -> readHandler() -> readCallback()
|
||||
}
|
||||
@ -1164,14 +1317,7 @@ timerExpired()
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef EPICS_3_14
|
||||
epicsTimerNotify::expireStatus AsynDriverInterface::
|
||||
expire(const epicsTime &)
|
||||
{
|
||||
timerExpired();
|
||||
return noRestart;
|
||||
}
|
||||
#else
|
||||
#ifdef EPICS_3_13
|
||||
void AsynDriverInterface::
|
||||
expire(CALLBACK *pcallback)
|
||||
{
|
||||
@ -1179,6 +1325,13 @@ expire(CALLBACK *pcallback)
|
||||
static_cast<AsynDriverInterface*>(pcallback->user);
|
||||
interface->timerExpired();
|
||||
}
|
||||
#else
|
||||
epicsTimerNotify::expireStatus AsynDriverInterface::
|
||||
expire(const epicsTime &)
|
||||
{
|
||||
timerExpired();
|
||||
return noRestart;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool AsynDriverInterface::
|
||||
@ -1277,6 +1430,9 @@ void handleRequest(asynUser* pasynUser)
|
||||
{
|
||||
AsynDriverInterface* interface =
|
||||
static_cast<AsynDriverInterface*>(pasynUser->userPvt);
|
||||
interface->cancelTimer();
|
||||
debug("AsynDriverInterface::handleRequest(%s) %s\n",
|
||||
interface->clientName(), ioActionStr[interface->ioAction]);
|
||||
switch (interface->ioAction)
|
||||
{
|
||||
case None:
|
||||
@ -1311,6 +1467,8 @@ void handleTimeout(asynUser* pasynUser)
|
||||
{
|
||||
AsynDriverInterface* interface =
|
||||
static_cast<AsynDriverInterface*>(pasynUser->userPvt);
|
||||
debug("AsynDriverInterface::handleTimeout(%s)\n",
|
||||
interface->clientName());
|
||||
switch (interface->ioAction)
|
||||
{
|
||||
case Lock:
|
||||
|
@ -65,32 +65,60 @@ printLong(const StreamFormat& format, StreamBuffer& output, long value)
|
||||
{
|
||||
// find number of significant bits
|
||||
prec = sizeof (value) * 8;
|
||||
while (prec && (value & (1 << (prec - 1))) == 0) prec--;
|
||||
while (prec && (value & (1L << (prec - 1))) == 0) prec--;
|
||||
}
|
||||
if (prec == 0) prec++; // print at least one bit
|
||||
int width = prec;
|
||||
if (format.width > width) width = format.width;
|
||||
char zero = format.info[0];
|
||||
char one = format.info[1];
|
||||
if (!(format.flags & left_flag))
|
||||
char fill = (format.flags & zero_flag) ? zero : ' ';
|
||||
if (format.flags & alt_flag)
|
||||
{
|
||||
// pad left
|
||||
char fill = (format.flags & zero_flag) ? zero : ' ';
|
||||
while (width > prec)
|
||||
// little endian (least significant bit first)
|
||||
if (!(format.flags & left_flag))
|
||||
{
|
||||
output.append(fill);
|
||||
// pad left
|
||||
while (width > prec)
|
||||
{
|
||||
output.append(' ');
|
||||
width--;
|
||||
}
|
||||
}
|
||||
while (prec--)
|
||||
{
|
||||
output.append((value & 1) ? one : zero);
|
||||
value >>= 1;
|
||||
width--;
|
||||
}
|
||||
while (width--)
|
||||
{
|
||||
// pad right
|
||||
output.append(fill);
|
||||
}
|
||||
}
|
||||
while (prec--)
|
||||
else
|
||||
{
|
||||
output.append((value & (1 << prec)) ? one : zero);
|
||||
width--;
|
||||
}
|
||||
while (width--)
|
||||
{
|
||||
// pad right
|
||||
output.append(' ');
|
||||
// big endian (most significant bit first)
|
||||
if (!(format.flags & left_flag))
|
||||
{
|
||||
// pad left
|
||||
while (width > prec)
|
||||
{
|
||||
output.append(fill);
|
||||
width--;
|
||||
}
|
||||
}
|
||||
while (prec--)
|
||||
{
|
||||
output.append((value & (1L << prec)) ? one : zero);
|
||||
width--;
|
||||
}
|
||||
while (width--)
|
||||
{
|
||||
// pad right
|
||||
output.append(' ');
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -102,14 +130,29 @@ scanLong(const StreamFormat& format, const char* input, long& value)
|
||||
int width = format.width;
|
||||
if (width == 0) width = -1;
|
||||
int length = 0;
|
||||
while (isspace(input[++length])); // skip whitespaces
|
||||
char zero = format.info[0];
|
||||
char one = format.info[1];
|
||||
if (!isspace(zero) && !isspace(one))
|
||||
while (isspace(input[length])) length++; // skip whitespaces
|
||||
if (input[length] != zero && input[length] != one) return -1;
|
||||
while (width-- && (input[length] == zero || input[length] == one))
|
||||
if (format.flags & alt_flag)
|
||||
{
|
||||
val <<= 1;
|
||||
if (input[length++] == one) val |= 1;
|
||||
// little endian (least significan bit first)
|
||||
long mask = 1;
|
||||
while (width-- && (input[length] == zero || input[length] == one))
|
||||
{
|
||||
if (input[length++] == one) val |= mask;
|
||||
mask <<= 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// big endian (most significan bit first)
|
||||
while (width-- && (input[length] == zero || input[length] == one))
|
||||
{
|
||||
val <<= 1;
|
||||
if (input[length++] == one) val |= 1;
|
||||
}
|
||||
}
|
||||
value = val;
|
||||
return length;
|
||||
|
@ -13,7 +13,7 @@ RECORDS += longout longin
|
||||
RECORDS += stringout stringin
|
||||
RECORDS += waveform
|
||||
RECORDS += calcout
|
||||
#RECORDS += aai aao
|
||||
RECORDS += aai aao
|
||||
|
||||
# Do you have synApps and want support for scalcout?
|
||||
# Then define CALC or SYNAPPS in your RELEASE file
|
||||
@ -22,6 +22,7 @@ RECORDS += calcout
|
||||
# you have to build the 'sscan' and 'genSub'
|
||||
# modules before building 'calc'.
|
||||
# See doc/scalcout.html for a required fix in scalcout.
|
||||
|
||||
SYNAPPS_RECORDS += scalcout
|
||||
|
||||
# You may add more bus interfaces
|
||||
@ -29,7 +30,9 @@ SYNAPPS_RECORDS += scalcout
|
||||
# $(BUS)Interface.cc
|
||||
# asynDriver interface is added automatically
|
||||
# if ASYN is defined in your RELEASE file.
|
||||
# BUSSES += Debug
|
||||
|
||||
BUSSES += Debug
|
||||
BUSSES += Dummy
|
||||
|
||||
# You may add more format converters
|
||||
# This requires the naming convention
|
||||
@ -38,9 +41,11 @@ SYNAPPS_RECORDS += scalcout
|
||||
FORMATS += Enum
|
||||
FORMATS += BCD
|
||||
FORMATS += Raw
|
||||
FORMATS += RawFloat
|
||||
FORMATS += Binary
|
||||
FORMATS += Checksum
|
||||
FORMATS += Exponential
|
||||
FORMATS += MantissaExponent
|
||||
FORMATS += Timestamp
|
||||
|
||||
# Want Perl regular expression matching?
|
||||
# If PCRE is installed at the same location for all
|
||||
@ -65,10 +70,6 @@ endif
|
||||
# by "static IoccrfReg iocshReg;"
|
||||
# LOADABLE_MODULE = YES
|
||||
|
||||
# Want to add some memory tracing
|
||||
# to find memory leaks?
|
||||
# USE_MEMGUARD = YES
|
||||
|
||||
# Sources of Stream Kernel
|
||||
# Do not modify
|
||||
|
||||
|
@ -265,7 +265,7 @@ static ulong crc_0x04C11DB7(const uchar* data, ulong len, ulong crc)
|
||||
{
|
||||
// x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 +
|
||||
// x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + x^0 (0x04C11DB7)
|
||||
const static ulong table[] = {
|
||||
const static unsigned int table[] = {
|
||||
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
|
||||
0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
|
||||
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
|
||||
@ -340,7 +340,7 @@ static ulong crc_0x04C11DB7_r(const uchar* data, ulong len, ulong crc)
|
||||
// x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 +
|
||||
// x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + x^0 (0x04C11DB7)
|
||||
// reflected
|
||||
const static ulong table[] = {
|
||||
const static unsigned int table[] = {
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
|
||||
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
|
||||
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
||||
@ -488,6 +488,9 @@ static checksum checksumMap[] =
|
||||
{"crc16r", crc_0x8005_r, 0x0000, 0x0000, 2}, // 0xBB3D
|
||||
{"ccitt16", crc_0x1021, 0xFFFF, 0x0000, 2}, // 0x29B1
|
||||
{"ccitt16a",crc_0x1021, 0x1D0F, 0x0000, 2}, // 0xE5CC
|
||||
{"ccitt16x",crc_0x1021, 0x0000, 0x0000, 2}, // 0x31C3
|
||||
{"crc16c", crc_0x1021, 0x0000, 0x0000, 2}, // 0x31C3
|
||||
{"xmodem", crc_0x1021, 0x0000, 0x0000, 2}, // 0x31C3
|
||||
{"crc32", crc_0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF, 4}, // 0xFC891918
|
||||
{"crc32r", crc_0x04C11DB7_r, 0xFFFFFFFF, 0xFFFFFFFF, 4}, // 0xCBF43926
|
||||
{"jamcrc", crc_0x04C11DB7_r, 0xFFFFFFFF, 0x00000000, 4}, // 0x340BC6D9
|
||||
@ -542,9 +545,9 @@ printPseudo(const StreamFormat& format, StreamBuffer& output)
|
||||
debug("ChecksumConverter %s: output to check: \"%s\"\n",
|
||||
checksumMap[fnum].name, output.expand(start,length)());
|
||||
|
||||
sum = checksumMap[fnum].xorout ^ checksumMap[fnum].func(
|
||||
sum = (checksumMap[fnum].xorout ^ checksumMap[fnum].func(
|
||||
reinterpret_cast<uchar*>(output(start)), length,
|
||||
checksumMap[fnum].init) & mask[checksumMap[fnum].bytes];
|
||||
checksumMap[fnum].init)) & mask[checksumMap[fnum].bytes];
|
||||
|
||||
debug("ChecksumConverter %s: output checksum is 0x%lX\n",
|
||||
checksumMap[fnum].name, sum);
|
||||
@ -552,6 +555,17 @@ printPseudo(const StreamFormat& format, StreamBuffer& output)
|
||||
int i;
|
||||
unsigned outchar;
|
||||
|
||||
if (format.flags & sign_flag) // decimal
|
||||
{
|
||||
// get number of decimal digits from number of bytes: ceil(xbytes*2.5)
|
||||
i = (checksumMap[fnum].bytes+1)*25/10-2;
|
||||
output.print("%0*ld", i, sum);
|
||||
debug("ChecksumConverter %s: decimal appending %0*ld\n",
|
||||
checksumMap[fnum].name, i, sum);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (format.flags & alt_flag) // lsb first (little endian)
|
||||
{
|
||||
for (i = 0; i < checksumMap[fnum].bytes; i++)
|
||||
@ -560,7 +574,7 @@ printPseudo(const StreamFormat& format, StreamBuffer& output)
|
||||
debug("ChecksumConverter %s: little endian appending 0x%X\n",
|
||||
checksumMap[fnum].name, outchar);
|
||||
if (format.flags & zero_flag) // ASCII
|
||||
output.printf("%02X", outchar);
|
||||
output.print("%02X", outchar);
|
||||
else // binary
|
||||
output.append(outchar);
|
||||
sum >>= 8;
|
||||
@ -575,7 +589,7 @@ printPseudo(const StreamFormat& format, StreamBuffer& output)
|
||||
debug("ChecksumConverter %s: big endian appending 0x%X\n",
|
||||
checksumMap[fnum].name, outchar);
|
||||
if (format.flags & zero_flag) // ASCII
|
||||
output.printf("%02X", outchar);
|
||||
output.print("%02X", outchar);
|
||||
else // binary
|
||||
output.append(outchar);
|
||||
sum <<= 8;
|
||||
@ -603,9 +617,9 @@ scanPseudo(const StreamFormat& format, StreamBuffer& input, long& cursor)
|
||||
return -1;
|
||||
}
|
||||
|
||||
sum = checksumMap[fnum].xorout ^ checksumMap[fnum].func(
|
||||
sum = (checksumMap[fnum].xorout ^ checksumMap[fnum].func(
|
||||
reinterpret_cast<uchar*>(input(start)), length,
|
||||
checksumMap[fnum].init) & mask[checksumMap[fnum].bytes];
|
||||
checksumMap[fnum].init)) & mask[checksumMap[fnum].bytes];
|
||||
|
||||
debug("ChecksumConverter %s: input checksum is 0x%0*lX\n",
|
||||
checksumMap[fnum].name, 2*checksumMap[fnum].bytes, sum);
|
||||
@ -613,6 +627,24 @@ scanPseudo(const StreamFormat& format, StreamBuffer& input, long& cursor)
|
||||
int i,j;
|
||||
unsigned inchar;
|
||||
|
||||
if (format.flags & sign_flag) // decimal
|
||||
{
|
||||
ulong sumin = 0;
|
||||
// get number of decimal digits from number of bytes: ceil(xbytes*2.5)
|
||||
j = (checksumMap[fnum].bytes+1)*25/10-2;
|
||||
for (i = 0; i < j; i++)
|
||||
{
|
||||
inchar = input[cursor+i];
|
||||
if (isdigit(inchar)) sumin = sumin*10+inchar-'0';
|
||||
else break;
|
||||
}
|
||||
if (sumin==sum) return i;
|
||||
error("Input %0*lu does not match checksum %0*lu\n",
|
||||
i, sumin, j, sum);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
if (format.flags & alt_flag) // lsb first (little endian)
|
||||
{
|
||||
for (i = 0; i < checksumMap[fnum].bytes; i++)
|
||||
|
76
src/DummyInterface.cc
Normal file
@ -0,0 +1,76 @@
|
||||
/***************************************************************
|
||||
* StreamDevice Support *
|
||||
* *
|
||||
* (C) 2011 Dirk Zimoch (dirk.zimoch@psi.ch) *
|
||||
* *
|
||||
* This is the interface to a "dummy" bus driver for *
|
||||
* StreamDevice. It does not provide any I/O functionality. *
|
||||
* *
|
||||
* If you do any changes in this file, you are not allowed to *
|
||||
* redistribute it any more. If there is a bug or a missing *
|
||||
* feature, send me an email and/or your patch. If I accept *
|
||||
* your changes, they will go to the next release. *
|
||||
* *
|
||||
* DISCLAIMER: If this software breaks something or harms *
|
||||
* someone, it's your problem. *
|
||||
* *
|
||||
***************************************************************/
|
||||
|
||||
#include "StreamBusInterface.h"
|
||||
#include "StreamError.h"
|
||||
#include "StreamBuffer.h"
|
||||
|
||||
class DummyInterface : StreamBusInterface
|
||||
{
|
||||
DummyInterface(Client* client);
|
||||
|
||||
// StreamBusInterface methods
|
||||
bool lockRequest(unsigned long lockTimeout_ms);
|
||||
bool unlock();
|
||||
|
||||
protected:
|
||||
~DummyInterface();
|
||||
|
||||
public:
|
||||
// static creator method
|
||||
static StreamBusInterface* getBusInterface(Client* client,
|
||||
const char* busname, int addr, const char* param);
|
||||
};
|
||||
|
||||
RegisterStreamBusInterface(DummyInterface);
|
||||
|
||||
DummyInterface::
|
||||
DummyInterface(Client* client) : StreamBusInterface(client)
|
||||
{
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
DummyInterface::
|
||||
~DummyInterface()
|
||||
{
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
StreamBusInterface* DummyInterface::
|
||||
getBusInterface(Client* client,
|
||||
const char* busname, int addr, const char*)
|
||||
{
|
||||
if (strcmp(busname, "dummy") == 0)
|
||||
{
|
||||
DummyInterface* interface = new DummyInterface(client);
|
||||
return interface;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool DummyInterface::
|
||||
lockRequest(unsigned long lockTimeout_ms)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DummyInterface::
|
||||
unlock()
|
||||
{
|
||||
return false;
|
||||
}
|
@ -21,6 +21,7 @@
|
||||
#include "StreamFormatConverter.h"
|
||||
#include "StreamError.h"
|
||||
#include "StreamProtocol.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
// Enum %{string0|string1|...}
|
||||
|
||||
@ -31,44 +32,62 @@ class EnumConverter : public StreamFormatConverter
|
||||
int scanLong(const StreamFormat&, const char*, long&);
|
||||
};
|
||||
|
||||
// info format: <numEnums><index><string>0<index><string>0...
|
||||
|
||||
int EnumConverter::
|
||||
parse(const StreamFormat& fmt, StreamBuffer& info,
|
||||
const char*& source, bool)
|
||||
{
|
||||
if (fmt.flags & (left_flag|sign_flag|space_flag|zero_flag|alt_flag))
|
||||
if (fmt.flags & (left_flag|sign_flag|space_flag|zero_flag))
|
||||
{
|
||||
error("Use of modifiers '-', '+', ' ', '0', '#'"
|
||||
error("Use of modifiers '-', '+', ' ', '0' "
|
||||
"not allowed with %%{ conversion\n");
|
||||
return false;
|
||||
}
|
||||
int i = info.length(); // put maxValue here later
|
||||
info.append('\0');
|
||||
int maxValue = 0;
|
||||
long numEnums = 0;
|
||||
int n = info.length(); // put numEnums here later
|
||||
info.append(&numEnums, sizeof(numEnums));
|
||||
long index = 0;
|
||||
int i = 0;
|
||||
i = info.length(); // put index here later
|
||||
info.append(&index, sizeof(index));
|
||||
while (*source)
|
||||
{
|
||||
switch (*source)
|
||||
if (*source == '=' && (fmt.flags & alt_flag))
|
||||
{
|
||||
case '|':
|
||||
info.append('\0');
|
||||
if (++maxValue > 255)
|
||||
{
|
||||
error("Too many enums (max 256)\n");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '}':
|
||||
source++;
|
||||
info.append('\0');
|
||||
info[i] = maxValue;
|
||||
debug("EnumConverter::parse %d choices: %s\n",
|
||||
maxValue+1, info.expand(i+1)());
|
||||
return enum_format;
|
||||
case esc:
|
||||
info.append(*source++);
|
||||
default:
|
||||
info.append(*source);
|
||||
char* p;
|
||||
index = strtol(++source, &p, 0);
|
||||
if (p == source || (*p != '|' && *p != '}'))
|
||||
{
|
||||
error("Integer expected after '=' "
|
||||
"in %%{ format conversion\n");
|
||||
return false;
|
||||
}
|
||||
memcpy(info(i), &index, sizeof(index));
|
||||
source = p;
|
||||
}
|
||||
if (*source == '|' || *source == '}')
|
||||
{
|
||||
numEnums++;
|
||||
info.append('\0');
|
||||
|
||||
if (*source++ == '}')
|
||||
{
|
||||
memcpy(info(n), &numEnums, sizeof(numEnums));
|
||||
debug("EnumConverter::parse %ld choices: %s\n",
|
||||
numEnums, info.expand()());
|
||||
return enum_format;
|
||||
}
|
||||
index ++;
|
||||
i = info.length();
|
||||
info.append(&index, sizeof(index));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (*source == esc)
|
||||
info.append(*source++);
|
||||
info.append(*source++);
|
||||
}
|
||||
source++;
|
||||
}
|
||||
error("Missing '}' after %%{ format conversion\n");
|
||||
return false;
|
||||
@ -77,14 +96,10 @@ parse(const StreamFormat& fmt, StreamBuffer& info,
|
||||
bool EnumConverter::
|
||||
printLong(const StreamFormat& fmt, StreamBuffer& output, long value)
|
||||
{
|
||||
long maxValue = fmt.info[0]; // number of enums
|
||||
const char* s = fmt.info+1; // first enum string
|
||||
if (value < 0 || value > maxValue)
|
||||
{
|
||||
error("Value %li out of range [0...%li]\n", value, maxValue);
|
||||
return false;
|
||||
}
|
||||
while (value--)
|
||||
const char* s = fmt.info;
|
||||
long numEnums = extract<long>(s);
|
||||
long index = extract<long>(s);
|
||||
while (numEnums-- && (value != index))
|
||||
{
|
||||
while(*s)
|
||||
{
|
||||
@ -92,6 +107,12 @@ printLong(const StreamFormat& fmt, StreamBuffer& output, long value)
|
||||
s++;
|
||||
}
|
||||
s++;
|
||||
index = extract<long>(s);
|
||||
}
|
||||
if (numEnums == -1)
|
||||
{
|
||||
error("Value %li not found in enum set\n", value);
|
||||
return false;
|
||||
}
|
||||
while(*s)
|
||||
{
|
||||
@ -106,14 +127,16 @@ scanLong(const StreamFormat& fmt, const char* input, long& value)
|
||||
{
|
||||
debug("EnumConverter::scanLong(%%%c, \"%s\")\n",
|
||||
fmt.conv, input);
|
||||
long maxValue = fmt.info[0]; // number of enums
|
||||
const char* s = fmt.info+1; // first enum string
|
||||
const char* s = fmt.info;
|
||||
long numEnums = extract<long>(s);
|
||||
long index;
|
||||
int length;
|
||||
long val;
|
||||
|
||||
bool match;
|
||||
for (val = 0; val <= maxValue; val++)
|
||||
while (numEnums--)
|
||||
{
|
||||
debug("EnumConverter::scanLong: check #%ld \"%s\"\n", val, s);
|
||||
index = extract<long>(s);
|
||||
debug("EnumConverter::scanLong: check #%ld \"%s\"\n", index, s);
|
||||
length = 0;
|
||||
match = true;
|
||||
while(*s)
|
||||
@ -129,8 +152,8 @@ scanLong(const StreamFormat& fmt, const char* input, long& value)
|
||||
}
|
||||
if (match)
|
||||
{
|
||||
debug("EnumConverter::scanLong: value %ld matches\n", val);
|
||||
value = val;
|
||||
debug("EnumConverter::scanLong: value %ld matches\n", index);
|
||||
value = index;
|
||||
return length;
|
||||
}
|
||||
s++;
|
||||
|
@ -1,63 +0,0 @@
|
||||
/***************************************************************
|
||||
* StreamDevice Support *
|
||||
* *
|
||||
* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
|
||||
* (C) 2007 Dirk Zimoch (dirk.zimoch@psi.ch) *
|
||||
* *
|
||||
* This is a custom exponential format converter for *
|
||||
* StreamDevice. *
|
||||
* The number is represented as two signed integers, mantissa *
|
||||
* and exponent, like in +00011-01 *
|
||||
* Please refer to the HTML files in ../doc/ for a detailed *
|
||||
* documentation. *
|
||||
* *
|
||||
* If you do any changes in this file, you are not allowed to *
|
||||
* redistribute it any more. If there is a bug or a missing *
|
||||
* feature, send me an email and/or your patch. If I accept *
|
||||
* your changes, they will go to the next release. *
|
||||
* *
|
||||
* DISCLAIMER: If this software breaks something or harms *
|
||||
* someone, it's your problem. *
|
||||
* *
|
||||
***************************************************************/
|
||||
|
||||
#include "StreamFormatConverter.h"
|
||||
#include "StreamError.h"
|
||||
#include <math.h>
|
||||
|
||||
// Exponential Converter %m: format +00351-02 means +351e-2
|
||||
|
||||
class ExponentialConverter : public StreamFormatConverter
|
||||
{
|
||||
virtual int parse(const StreamFormat&, StreamBuffer&, const char*&, bool);
|
||||
virtual int scanDouble(const StreamFormat&, const char*, double&);
|
||||
};
|
||||
|
||||
int ExponentialConverter::
|
||||
parse(const StreamFormat& fmt, StreamBuffer& info,
|
||||
const char*& source, bool scanFormat)
|
||||
{
|
||||
if (!scanFormat)
|
||||
{
|
||||
error("At the moment for %%m format only input is implemented\n");
|
||||
return false;
|
||||
}
|
||||
return double_format;
|
||||
}
|
||||
|
||||
int ExponentialConverter::
|
||||
scanDouble(const StreamFormat& fmt, const char* input, double& value)
|
||||
{
|
||||
int mantissa;
|
||||
int exponent;
|
||||
int length = -1;
|
||||
|
||||
sscanf(input, "%d%d%n", &mantissa, &exponent, &length);
|
||||
if (fmt.flags & skip_flag) return length;
|
||||
if (length == -1) return -1;
|
||||
value = (double)(mantissa) * pow(10, exponent);
|
||||
return length;
|
||||
}
|
||||
|
||||
RegisterConverter (ExponentialConverter, "m");
|
||||
|
37
src/Makefile
@ -65,18 +65,13 @@ ifdef PCRE
|
||||
LIB_LIBS += pcre
|
||||
else
|
||||
ifneq ($(words $(PCRE_LIB) $(PCRE_INCLUDE)),0)
|
||||
LIB_SYS_LIBS += pcre
|
||||
LIB_SYS_LIBS_DEFAULT += pcre
|
||||
LIB_SYS_LIBS_WIN32 += $(PCRE_LIB)\\pcre
|
||||
SHRLIB_DEPLIB_DIRS += $(PCRE_LIB)
|
||||
endif
|
||||
endif
|
||||
|
||||
LIB_LIBS += Com dbIoc dbStaticIoc registryIoc iocsh
|
||||
|
||||
ifeq ($(USE_MEMGUARD),YES)
|
||||
# memguard looks for memory leaks (gcc only)
|
||||
CPPFLAGS += -include ../memguard.h
|
||||
LIB_SRCS += memguard.cc
|
||||
endif
|
||||
LIB_LIBS += $(EPICS_BASE_IOC_LIBS)
|
||||
|
||||
INC += devStream.h
|
||||
|
||||
@ -84,31 +79,21 @@ include $(TOP)/configure/RULES
|
||||
|
||||
# Update version string (contains __DATE__ and __TIME__)
|
||||
# each time make runs.
|
||||
StreamVersion$(OBJ): FORCE
|
||||
FORCE:
|
||||
StreamVersion$(OBJ): ../*.c ../*.h ../*.cc ../CONFIG_STREAM ../Makefile
|
||||
|
||||
# Add references to all registrars to main file to avoid
|
||||
# missing initialization.
|
||||
StreamCore$(OBJ): streamReferences
|
||||
StreamCore$(OBJ) StreamCore$(DEP): streamReferences
|
||||
|
||||
streamReferences: ../CONFIG_STREAM
|
||||
@for i in $(BUSSES); \
|
||||
do echo "extern void* ref_$${i}Interface;"; \
|
||||
echo "void* p$$i = ref_$${i}Interface;"; \
|
||||
done > $@
|
||||
@for i in $(FORMATS); \
|
||||
do echo "extern void* ref_$${i}Converter;"; \
|
||||
echo "void* p$$i = ref_$${i}Converter;"; \
|
||||
done >> $@
|
||||
$(PERL) ../makeref.pl Interface $(BUSSES) > $@
|
||||
$(PERL) ../makeref.pl Converter $(FORMATS) >> $@
|
||||
|
||||
# create stream.dbd from all RECORDS
|
||||
$(COMMON_DIR)/$(LIBRARY_DEFAULT).dbd: ../CONFIG_STREAM
|
||||
@for r in $(RECORDS); \
|
||||
do echo "device($$r,INST_IO,dev$${r}Stream,\"stream\")"; \
|
||||
done > $@
|
||||
@echo "driver(stream)" >> $@
|
||||
@echo "variable(streamDebug, int)" >> $@
|
||||
@echo "variable(showAsyncErrors, int)" >> $@
|
||||
@echo "registrar(streamRegistrar)" >> $@
|
||||
$(PERL) ../makedbd.pl $(RECORDS) > $@
|
||||
|
||||
$(LIBRARY_DEFAULT).dbd$(DEP): ../CONFIG_STREAM
|
||||
echo $(LIBRARY_DEFAULT).dbd: $< > $@
|
||||
|
||||
endif
|
||||
|
@ -39,7 +39,4 @@ include $(TOP)/config/RULES.Host
|
||||
|
||||
# create stream.dbd from all RECORDS
|
||||
stream.dbd: ../CONFIG_STREAM
|
||||
@for r in $(RECORDS_3_13); \
|
||||
do echo "device($$r,INST_IO,dev$${r}Stream,\"stream\")"; \
|
||||
done > $@
|
||||
@echo "driver(stream)" >> $@
|
||||
$(PERL) ../makedbd.pl -3.13 $(RECORDS_3_13) > $@
|
||||
|
100
src/MantissaExponentConverter.cc
Normal file
@ -0,0 +1,100 @@
|
||||
/***************************************************************
|
||||
* StreamDevice Support *
|
||||
* *
|
||||
* (C) 2008 Dirk Zimoch (dirk.zimoch@psi.ch) *
|
||||
* *
|
||||
* This is a custom exponential format converter for *
|
||||
* StreamDevice. *
|
||||
* The number is represented as two signed integers, mantissa *
|
||||
* and exponent, like in +00011-01 *
|
||||
* Please refer to the HTML files in ../doc/ for a detailed *
|
||||
* documentation. *
|
||||
* *
|
||||
* If you do any changes in this file, you are not allowed to *
|
||||
* redistribute it any more. If there is a bug or a missing *
|
||||
* feature, send me an email and/or your patch. If I accept *
|
||||
* your changes, they will go to the next release. *
|
||||
* *
|
||||
* DISCLAIMER: If this software breaks something or harms *
|
||||
* someone, it's your problem. *
|
||||
* *
|
||||
***************************************************************/
|
||||
|
||||
#include "StreamFormatConverter.h"
|
||||
#include "StreamError.h"
|
||||
#include <math.h>
|
||||
|
||||
// Exponential Converter %m
|
||||
// Eric Berryman requested a double format that reads
|
||||
// +00011-01 as 11e-01
|
||||
// I.e integer mantissa and exponent without 'e' or '.'
|
||||
// But why not +11000-04 ?
|
||||
// For writing, I chose the following convention:
|
||||
// Format precision defines number of digits in mantissa
|
||||
// No leading '0' in mantissa (except for 0.0 of course)
|
||||
// Number of digits in exponent is at least 2
|
||||
// Format flags +, -, and space are supported in the usual way
|
||||
// Flags #, 0 are not supported
|
||||
|
||||
class MantissaExponentConverter : public StreamFormatConverter
|
||||
{
|
||||
virtual int parse(const StreamFormat&, StreamBuffer&, const char*&, bool);
|
||||
virtual int scanDouble(const StreamFormat&, const char*, double&);
|
||||
virtual bool printDouble(const StreamFormat&, StreamBuffer&, double);
|
||||
};
|
||||
|
||||
int MantissaExponentConverter::
|
||||
parse(const StreamFormat&, StreamBuffer&,
|
||||
const char*&, bool)
|
||||
{
|
||||
return double_format;
|
||||
}
|
||||
|
||||
int MantissaExponentConverter::
|
||||
scanDouble(const StreamFormat& fmt, const char* input, double& value)
|
||||
{
|
||||
int mantissa;
|
||||
int exponent;
|
||||
int length = -1;
|
||||
|
||||
sscanf(input, "%d%d%n", &mantissa, &exponent, &length);
|
||||
if (fmt.flags & skip_flag) return length;
|
||||
if (length == -1) return -1;
|
||||
value = (double)(mantissa) * pow(10.0, exponent);
|
||||
return length;
|
||||
}
|
||||
|
||||
bool MantissaExponentConverter::
|
||||
printDouble(const StreamFormat& fmt, StreamBuffer& output, double value)
|
||||
{
|
||||
// Have to divide value into mantissa and exponent
|
||||
// precision field is number of characters in mantissa
|
||||
// number of characters in exponent is at least 2
|
||||
int spaces;
|
||||
StreamBuffer buf;
|
||||
int prec = fmt.prec;
|
||||
|
||||
if (prec < 1) prec = 6;
|
||||
buf.print("%.*e", prec-1, fabs(value)/pow(10.0, prec-1));
|
||||
buf.remove(1,1);
|
||||
buf.remove(buf.find('e'),1);
|
||||
|
||||
spaces = fmt.width-buf.length();
|
||||
if (fmt.flags & (space_flag|sign_flag) || value < 0.0) spaces--;
|
||||
if (spaces < 0) spaces = 0;
|
||||
if (!(fmt.flags & left_flag))
|
||||
output.append(' ', spaces);
|
||||
if ((fmt.flags & (space_flag|sign_flag)) == space_flag && value >= 0.0)
|
||||
output.append(' ');
|
||||
if (fmt.flags & sign_flag && value >= 0.0)
|
||||
output.append('+');
|
||||
if (value < 0.0)
|
||||
output.append('-');
|
||||
output.append(buf);
|
||||
if (fmt.flags & left_flag)
|
||||
output.append(' ', spaces);
|
||||
return true;
|
||||
}
|
||||
|
||||
RegisterConverter (MantissaExponentConverter, "m");
|
||||
|
@ -40,12 +40,14 @@ parse(const StreamFormat&, StreamBuffer&,
|
||||
bool RawConverter::
|
||||
printLong(const StreamFormat& format, StreamBuffer& output, long value)
|
||||
{
|
||||
int prec = format.prec; // number of bytes from value
|
||||
if (prec == -1) prec = 1; // default: 1 byte
|
||||
int width = prec; // number of bytes in output
|
||||
int prec = format.prec; // number of bytes from value
|
||||
if (prec == -1) prec = 1; // default: 1 byte
|
||||
int width = prec; // number of bytes in output
|
||||
if (prec > (int)sizeof(long)) prec=sizeof(long);
|
||||
if (format.width > width) width = format.width;
|
||||
|
||||
char byte = 0;
|
||||
if (format.flags & alt_flag) // lsb first (little endian)
|
||||
if (format.flags & alt_flag) // little endian (lsb first)
|
||||
{
|
||||
while (prec--)
|
||||
{
|
||||
@ -69,7 +71,7 @@ printLong(const StreamFormat& format, StreamBuffer& output, long value)
|
||||
output.append(byte);
|
||||
}
|
||||
}
|
||||
else // msb first (big endian)
|
||||
else // big endian (msb first)
|
||||
{
|
||||
if (format.flags & zero_flag)
|
||||
{
|
||||
@ -107,7 +109,7 @@ scanLong(const StreamFormat& format, const char* input, long& value)
|
||||
}
|
||||
if (format.flags & alt_flag)
|
||||
{
|
||||
// little endian (sign extended)*/
|
||||
// little endian (lsb first)
|
||||
unsigned int shift = 0;
|
||||
while (--width && shift < sizeof(long)*8)
|
||||
{
|
||||
@ -131,7 +133,7 @@ scanLong(const StreamFormat& format, const char* input, long& value)
|
||||
}
|
||||
else
|
||||
{
|
||||
// big endian */
|
||||
// big endian (msb first)
|
||||
if (format.flags & zero_flag)
|
||||
{
|
||||
// fill with zero
|
||||
|
136
src/RawFloatConverter.cc
Normal file
@ -0,0 +1,136 @@
|
||||
/***************************************************************
|
||||
* StreamDevice Support *
|
||||
* *
|
||||
* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
|
||||
* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
|
||||
* *
|
||||
* This is the raw format converter of StreamDevice. *
|
||||
* Please refer to the HTML files in ../doc/ for a detailed *
|
||||
* documentation. *
|
||||
* *
|
||||
* If you do any changes in this file, you are not allowed to *
|
||||
* redistribute it any more. If there is a bug or a missing *
|
||||
* feature, send me an email and/or your patch. If I accept *
|
||||
* your changes, they will go to the next release. *
|
||||
* *
|
||||
* DISCLAIMER: If this software breaks something or harms *
|
||||
* someone, it's your problem. *
|
||||
* *
|
||||
***************************************************************/
|
||||
|
||||
#include "StreamFormatConverter.h"
|
||||
#include "StreamError.h"
|
||||
|
||||
static int endian = 0;
|
||||
|
||||
// Raw Float Converter %R
|
||||
|
||||
class RawFloatConverter : public StreamFormatConverter
|
||||
{
|
||||
int parse(const StreamFormat&, StreamBuffer&, const char*&, bool);
|
||||
bool printDouble(const StreamFormat&, StreamBuffer&, double);
|
||||
int scanDouble(const StreamFormat&, const char*, double&);
|
||||
};
|
||||
|
||||
int RawFloatConverter::
|
||||
parse(const StreamFormat& format, StreamBuffer&,
|
||||
const char*&, bool)
|
||||
{
|
||||
// Find out byte order
|
||||
if (!endian) {
|
||||
union {long l; char c [sizeof(long)];} u;
|
||||
u.l=1;
|
||||
if (u.c[0]) { endian = 1234;} // little endian
|
||||
else if (u.c[sizeof(long)-1]) { endian = 4321;} // big endian
|
||||
else {
|
||||
error ("Cannot find out byte order for %%R format.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Assume IEEE formats with 4 or 8 bytes (default: 4)
|
||||
if (format.width==0 || format.width==4 || format.width==8)
|
||||
return double_format;
|
||||
error ("Only width 4 or 8 allowed for %%R format.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RawFloatConverter::
|
||||
printDouble(const StreamFormat& format, StreamBuffer& output, double value)
|
||||
{
|
||||
int nbOfBytes;
|
||||
int n;
|
||||
union {
|
||||
double dval;
|
||||
float fval;
|
||||
char bytes[8];
|
||||
} buffer;
|
||||
|
||||
nbOfBytes = format.width;
|
||||
if (nbOfBytes == 0)
|
||||
nbOfBytes = 4;
|
||||
|
||||
if (nbOfBytes == 4)
|
||||
buffer.fval = (float)value;
|
||||
else
|
||||
buffer.dval = value;
|
||||
|
||||
if (!(format.flags & alt_flag) ^ (endian == 4321))
|
||||
{
|
||||
// swap if byte orders differ
|
||||
for (n = nbOfBytes-1; n >= 0; n--)
|
||||
{
|
||||
output.append(buffer.bytes[n]);
|
||||
}
|
||||
} else {
|
||||
for (n = 0; n < nbOfBytes; n++)
|
||||
{
|
||||
output.append(buffer.bytes[n]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int RawFloatConverter::
|
||||
scanDouble(const StreamFormat& format, const char* input, double& value)
|
||||
{
|
||||
int nbOfBytes;
|
||||
int i, n;
|
||||
|
||||
union {
|
||||
double dval;
|
||||
float fval;
|
||||
char bytes[8];
|
||||
} buffer;
|
||||
|
||||
nbOfBytes = format.width;
|
||||
if (nbOfBytes == 0)
|
||||
nbOfBytes = 4;
|
||||
|
||||
if (format.flags & skip_flag)
|
||||
{
|
||||
return(nbOfBytes); // just skip input
|
||||
}
|
||||
|
||||
if (!(format.flags & alt_flag) ^ (endian == 4321))
|
||||
{
|
||||
// swap if byte orders differ
|
||||
for (n = nbOfBytes-1, i = 0; n >= 0; n--, i++)
|
||||
{
|
||||
buffer.bytes[n] = input[i];
|
||||
}
|
||||
} else {
|
||||
for (n = 0; n < nbOfBytes; n++)
|
||||
{
|
||||
buffer.bytes[n] = input[n];
|
||||
}
|
||||
}
|
||||
|
||||
if (nbOfBytes == 4)
|
||||
value = buffer.fval;
|
||||
else
|
||||
value = buffer.dval;
|
||||
|
||||
return nbOfBytes;
|
||||
}
|
||||
|
||||
RegisterConverter (RawFloatConverter, "R");
|
@ -70,7 +70,7 @@ parse(const StreamFormat& fmt, StreamBuffer& info,
|
||||
}
|
||||
if (*source == esc) {
|
||||
source++;
|
||||
pattern.printf("\\x%02x", *source++ & 0xFF);
|
||||
pattern.print("\\x%02x", *source++ & 0xFF);
|
||||
continue;
|
||||
}
|
||||
pattern.append(*source++);
|
||||
@ -110,9 +110,9 @@ scanString(const StreamFormat& fmt, const char* input,
|
||||
len = ovector[subexpr*2+1] - ovector[subexpr*2];
|
||||
if (len >= maxlen) {
|
||||
if (!(fmt.flags & sign_flag)) {
|
||||
debug("Matching string \"%s\" too long (%d>%d bytes)\n",
|
||||
error("Regexp: Matching string \"%s\" too long (%d>%d bytes). You may want to try the + flag: \"%%+/.../\"\n",
|
||||
StreamBuffer(input+ovector[subexpr*2], len).expand()(),
|
||||
len, maxlen-1);
|
||||
(int)len, (int)maxlen-1);
|
||||
return -1;
|
||||
}
|
||||
len = maxlen-1;
|
||||
|
@ -46,7 +46,7 @@ init(const void* s, long minsize)
|
||||
else
|
||||
{
|
||||
// clear local buffer
|
||||
memset(buffer+minsize, 0, cap-minsize);
|
||||
memset(buffer, 0, cap);
|
||||
}
|
||||
if (s) {
|
||||
len = minsize;
|
||||
@ -54,6 +54,13 @@ init(const void* s, long minsize)
|
||||
}
|
||||
}
|
||||
|
||||
// How the buffer looks like:
|
||||
// |----free-----|####used####|-------free-------|
|
||||
///|<--- offs -->|<-- len --->|<- cap-offs-len ->|
|
||||
// 0 offs offs+len cap
|
||||
// |<-------------- minsize --------------->
|
||||
|
||||
|
||||
void StreamBuffer::
|
||||
grow(long minsize)
|
||||
{
|
||||
@ -102,7 +109,7 @@ grow(long minsize)
|
||||
memset(newbuffer+len, 0, newcap-len);
|
||||
if (buffer != local)
|
||||
{
|
||||
delete buffer;
|
||||
delete [] buffer;
|
||||
}
|
||||
buffer = newbuffer;
|
||||
cap = newcap;
|
||||
@ -135,7 +142,7 @@ find(const void* m, long size, long start) const
|
||||
start += len;
|
||||
if (start < 0) start = 0;
|
||||
}
|
||||
if (start >= len-size+1) return -1; // find nothing after end
|
||||
if (start+size > len) return -1; // find nothing after end
|
||||
if (!m || size <= 0) return start; // find empty string at start
|
||||
const char* s = static_cast<const char*>(m);
|
||||
char* b = buffer+offs;
|
||||
@ -143,12 +150,12 @@ find(const void* m, long size, long start) const
|
||||
long i;
|
||||
while ((p = static_cast<char*>(memchr(p, s[0], b-p+len-size+1))))
|
||||
{
|
||||
i = 1;
|
||||
while (p[i] == s[i])
|
||||
for (i = 1; i < size; i++)
|
||||
{
|
||||
if (++i >= size) return p-b;
|
||||
if (p[i] != s[i]) goto next;
|
||||
}
|
||||
p++;
|
||||
return p-b;
|
||||
next: p++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@ -212,7 +219,7 @@ replace(long remstart, long remlen, const void* ins, long inslen)
|
||||
memset(newbuffer+newlen, 0, newcap-newlen);
|
||||
if (buffer != local)
|
||||
{
|
||||
delete buffer;
|
||||
delete [] buffer;
|
||||
}
|
||||
buffer = newbuffer;
|
||||
cap = newcap;
|
||||
@ -241,7 +248,7 @@ replace(long remstart, long remlen, const void* ins, long inslen)
|
||||
}
|
||||
|
||||
StreamBuffer& StreamBuffer::
|
||||
printf(const char* fmt, ...)
|
||||
print(const char* fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
int printed;
|
||||
@ -266,9 +273,18 @@ StreamBuffer StreamBuffer::expand(long start, long length) const
|
||||
if (start < 0)
|
||||
{
|
||||
start += len;
|
||||
if (start < 0) start = 0;
|
||||
}
|
||||
end = length >= 0 ? start+length : len;
|
||||
if (length < 0)
|
||||
{
|
||||
start += length;
|
||||
length = -length;
|
||||
}
|
||||
if (start < 0)
|
||||
{
|
||||
length += start;
|
||||
start = 0;
|
||||
}
|
||||
end = start+length;
|
||||
if (end > len) end = len;
|
||||
StreamBuffer result((end-start)*2);
|
||||
start += offs;
|
||||
@ -280,7 +296,7 @@ StreamBuffer StreamBuffer::expand(long start, long length) const
|
||||
c = buffer[i];
|
||||
if ((c & 0x7f) < 0x20 || (c & 0x7f) == 0x7f)
|
||||
{
|
||||
result.printf("<%02x>", c & 0xff);
|
||||
result.print("<%02x>", c & 0xff);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -296,17 +312,17 @@ dump() const
|
||||
StreamBuffer result(256+cap*5);
|
||||
result.append("\033[0m");
|
||||
long i;
|
||||
result.printf("%ld,%ld,%ld:\033[37m", offs, len, cap);
|
||||
result.print("%ld,%ld,%ld:\033[37m", offs, len, cap);
|
||||
for (i = 0; i < cap; i++)
|
||||
{
|
||||
if (i == offs) result.append("\033[34m[\033[0m");
|
||||
if ((buffer[i] & 0x7f) < 0x20 || (buffer[i] & 0x7f) == 0x7f)
|
||||
{
|
||||
if (i < offs || i >= offs+len)
|
||||
result.printf(
|
||||
result.print(
|
||||
"<%02x>", buffer[i] & 0xff);
|
||||
else
|
||||
result.printf(
|
||||
result.print(
|
||||
"\033[34m<%02x>\033[37m", buffer[i] & 0xff);
|
||||
}
|
||||
else
|
||||
|
@ -21,7 +21,6 @@
|
||||
#define StreamBuffer_h
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifndef __GNUC__
|
||||
#define __attribute__(x)
|
||||
@ -29,32 +28,36 @@
|
||||
|
||||
class StreamBuffer
|
||||
{
|
||||
char local[64];
|
||||
long len;
|
||||
long cap;
|
||||
long offs;
|
||||
char* buffer;
|
||||
char local[64];
|
||||
|
||||
void grow(long);
|
||||
void init(const void*, long);
|
||||
void init(const void* s, long minsize);
|
||||
|
||||
void check(long size)
|
||||
{if (len+offs+size >= cap) grow(len+size);}
|
||||
|
||||
void grow(long minsize);
|
||||
|
||||
public:
|
||||
// Hints:
|
||||
// * Any index parameter (long) can be negative
|
||||
// meaning "count from end" (-1 is the last byte)
|
||||
// * Appending negative count deletes from end
|
||||
// * Any returned char* pointer becomes invalid when
|
||||
// the StreamBuffer is modified.
|
||||
// * End of StreamBuffer always contains 0x00 bytes
|
||||
// * Deleting from start and clearing is fast
|
||||
|
||||
StreamBuffer()
|
||||
{init(NULL, 0);}
|
||||
|
||||
StreamBuffer(const void*s, long size)
|
||||
StreamBuffer(const void* s, long size)
|
||||
{init(s, size);}
|
||||
|
||||
StreamBuffer(const char*s)
|
||||
StreamBuffer(const char* s)
|
||||
{init(s, s?strlen(s):0);}
|
||||
|
||||
StreamBuffer(const StreamBuffer& s)
|
||||
@ -68,10 +71,10 @@ public:
|
||||
|
||||
// operator (): get char* pointing to index
|
||||
const char* operator()(long index=0) const
|
||||
{buffer[offs+len]=0; return buffer+offs+(index<0?index+len:index);}
|
||||
{return buffer+offs+(index<0?index+len:index);}
|
||||
|
||||
char* operator()(long index=0)
|
||||
{buffer[offs+len]=0; return buffer+offs+(index<0?index+len:index);}
|
||||
{return buffer+offs+(index<0?index+len:index);}
|
||||
|
||||
// operator []: get byte at index
|
||||
char operator[](long index) const
|
||||
@ -103,12 +106,17 @@ public:
|
||||
// reserve: reserve size bytes of memory and return
|
||||
// pointer to that memory (for copying something to it)
|
||||
char* reserve(long size)
|
||||
{check(size); char* p=buffer+offs+len; len+=size; return p; }
|
||||
{check(size); char* p=buffer+offs+len; len+=size; return p;}
|
||||
|
||||
// append: append data at the end of the buffer
|
||||
StreamBuffer& append(char c)
|
||||
{check(1); buffer[offs+len++]=c; return *this;}
|
||||
|
||||
StreamBuffer& append(char c, long count)
|
||||
{if (count < 0) truncate(count);
|
||||
else {check(count); memset(buffer+offs+len, c, count); len+=count;}
|
||||
return *this;}
|
||||
|
||||
StreamBuffer& append(const void* s, long size);
|
||||
|
||||
StreamBuffer& append(const char* s)
|
||||
@ -116,6 +124,16 @@ public:
|
||||
|
||||
StreamBuffer& append(const StreamBuffer& s)
|
||||
{return append(s.buffer+s.offs, s.len);}
|
||||
|
||||
// operator += alias for set
|
||||
StreamBuffer& operator+=(char c)
|
||||
{return append(c);}
|
||||
|
||||
StreamBuffer& operator+=(const char* s)
|
||||
{return append(s);}
|
||||
|
||||
StreamBuffer& operator+=(const StreamBuffer& s)
|
||||
{return append(s);}
|
||||
|
||||
// set: clear buffer and fill with new data
|
||||
StreamBuffer& set(const void* s, long size)
|
||||
@ -127,7 +145,7 @@ public:
|
||||
StreamBuffer& set(const StreamBuffer& s)
|
||||
{clear(); return append(s.buffer+s.offs, s.len);}
|
||||
|
||||
// operator =: alias for set
|
||||
// operator = alias for set
|
||||
StreamBuffer& operator=(const char* s)
|
||||
{return set(s);}
|
||||
|
||||
@ -144,15 +162,16 @@ public:
|
||||
StreamBuffer& replace(long pos, long length, const StreamBuffer& s)
|
||||
{return replace(pos, length, s.buffer+s.offs, s.len);}
|
||||
|
||||
// replace: delete part of buffer
|
||||
// remove: delete from start/pos
|
||||
StreamBuffer& remove(long pos, long length)
|
||||
{return replace(pos, length, NULL, 0);}
|
||||
|
||||
// remove from start: no memset, no function call, fast!
|
||||
StreamBuffer& remove(long length)
|
||||
{if (length>len) length=len;
|
||||
offs+=length; len-=length; return *this;}
|
||||
|
||||
// replace: delete end of buffer
|
||||
// truncate: delete end of buffer
|
||||
StreamBuffer& truncate(long pos)
|
||||
{return replace(pos, len, NULL, 0);}
|
||||
|
||||
@ -169,7 +188,7 @@ public:
|
||||
StreamBuffer& insert(long pos, char c)
|
||||
{return replace(pos, 0, &c, 1);}
|
||||
|
||||
StreamBuffer& printf(const char* fmt, ...)
|
||||
StreamBuffer& print(const char* fmt, ...)
|
||||
__attribute__ ((format(printf,2,3)));
|
||||
|
||||
// find: get index of data in buffer or -1
|
||||
@ -188,17 +207,20 @@ public:
|
||||
long int find(const StreamBuffer& s, long start=0) const
|
||||
{return find(s.buffer+s.offs, s.len, start);}
|
||||
|
||||
// equals: returns true if first size bytes are equal
|
||||
bool equals(const void* s, long size) const
|
||||
// startswith: returns true if first size bytes are equal
|
||||
bool startswith(const void* s, long size) const
|
||||
{return len>=size ? memcmp(buffer+offs, s, size) == 0 : false;}
|
||||
|
||||
// equals: returns true if first string is equal (empty string match)
|
||||
bool equals(const char* s) const
|
||||
// startswith: returns true if first string is equal (empty string matches)
|
||||
bool startswith(const char* s) const
|
||||
{return len ? strcmp(buffer+offs, s) == 0 : !s || !*s;}
|
||||
|
||||
// expand: create copy of StreamBuffer where all nonprintable characters
|
||||
// are replaced by <xx> with xx being the hex code of the characters
|
||||
StreamBuffer expand(long start=0, long length=-1) const;
|
||||
StreamBuffer expand(long start, long length) const;
|
||||
|
||||
StreamBuffer expand(long start=0) const
|
||||
{return expand(start, len);}
|
||||
|
||||
// dump: debug function, like expand but also show the 'hidden' memory
|
||||
// before and after the real data. Uses colours.
|
||||
|
@ -20,7 +20,11 @@
|
||||
#include "StreamBusInterface.h"
|
||||
|
||||
const char* StreamIoStatusStr[] = {
|
||||
"StreamIoSuccess", "ioTimeout", "ioNoReply", "ioEnd", "ioFault"
|
||||
"StreamIoSuccess",
|
||||
"StreamIoTimeout",
|
||||
"StreamIoNoReply",
|
||||
"StreamIoEnd",
|
||||
"StreamIoFault"
|
||||
};
|
||||
|
||||
StreamBusInterfaceRegistrarBase* StreamBusInterfaceRegistrarBase::first;
|
||||
|
@ -52,42 +52,62 @@ public:
|
||||
protected:
|
||||
StreamBusInterface* businterface;
|
||||
bool busSupportsEvent() {
|
||||
return businterface->supportsEvent();
|
||||
if (businterface)
|
||||
return businterface->supportsEvent();
|
||||
else return false;
|
||||
}
|
||||
bool busSupportsAsyncRead() {
|
||||
return businterface->supportsAsyncRead();
|
||||
if (businterface)
|
||||
return businterface->supportsAsyncRead();
|
||||
else return false;
|
||||
}
|
||||
bool busAcceptEvent(unsigned long mask,
|
||||
unsigned long replytimeout_ms) {
|
||||
return businterface->acceptEvent(mask, replytimeout_ms);
|
||||
if (businterface)
|
||||
return businterface->acceptEvent(mask, replytimeout_ms);
|
||||
else return false;
|
||||
}
|
||||
void busRelease() {
|
||||
businterface->release();
|
||||
if (businterface)
|
||||
businterface->release();
|
||||
}
|
||||
bool busLockRequest(unsigned long timeout_ms) {
|
||||
return businterface->lockRequest(timeout_ms);
|
||||
if (businterface)
|
||||
return businterface->lockRequest(timeout_ms);
|
||||
else return false;
|
||||
}
|
||||
bool busUnlock() {
|
||||
return businterface->unlock();
|
||||
if (businterface)
|
||||
return businterface->unlock();
|
||||
else return false;
|
||||
}
|
||||
bool busWriteRequest(const void* output, size_t size,
|
||||
unsigned long timeout_ms) {
|
||||
return businterface->writeRequest(output, size, timeout_ms);
|
||||
if (businterface)
|
||||
return businterface->writeRequest(output, size, timeout_ms);
|
||||
else return false;
|
||||
}
|
||||
bool busReadRequest(unsigned long replytimeout_ms,
|
||||
unsigned long readtimeout_ms, long expectedLength,
|
||||
bool async) {
|
||||
return businterface->readRequest(replytimeout_ms,
|
||||
readtimeout_ms, expectedLength, async);
|
||||
if (businterface)
|
||||
return businterface->readRequest(replytimeout_ms,
|
||||
readtimeout_ms, expectedLength, async);
|
||||
else return false;
|
||||
}
|
||||
void busFinish() {
|
||||
businterface->finish();
|
||||
if (businterface)
|
||||
businterface->finish();
|
||||
}
|
||||
bool busConnectRequest(unsigned long timeout_ms) {
|
||||
return businterface->connectRequest(timeout_ms);
|
||||
if (businterface)
|
||||
return businterface->connectRequest(timeout_ms);
|
||||
else return false;
|
||||
}
|
||||
bool busDisconnect() {
|
||||
return businterface->disconnectRequest();
|
||||
if (businterface)
|
||||
return businterface->disconnectRequest();
|
||||
else return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -23,12 +23,6 @@
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <epicsExport.h>
|
||||
int showAsyncErrors = 0;
|
||||
extern "C" {
|
||||
epicsExportAddress(int, showAsyncErrors);
|
||||
}
|
||||
|
||||
enum Commands { end_cmd, in_cmd, out_cmd, wait_cmd, event_cmd, exec_cmd,
|
||||
connect_cmd, disconnect_cmd };
|
||||
const char* commandStr[] = { "end", "in", "out", "wait", "event", "exec",
|
||||
@ -45,7 +39,6 @@ static char* printCommands(StreamBuffer& buffer, const char* c)
|
||||
{
|
||||
unsigned long timeout;
|
||||
unsigned long eventnumber;
|
||||
unsigned short cmdlen;
|
||||
while (1)
|
||||
{
|
||||
switch(*c++)
|
||||
@ -64,22 +57,21 @@ static char* printCommands(StreamBuffer& buffer, const char* c)
|
||||
break;
|
||||
case wait_cmd:
|
||||
timeout = extract<unsigned long>(c);
|
||||
buffer.printf(" wait %ld;\n # ms", timeout);
|
||||
buffer.print(" wait %ld;\n # ms", timeout);
|
||||
break;
|
||||
case event_cmd:
|
||||
eventnumber = extract<unsigned long>(c);
|
||||
timeout = extract<unsigned long>(c);
|
||||
buffer.printf(" event(%ld) %ld; # ms\n", eventnumber, timeout);
|
||||
buffer.print(" event(%ld) %ld; # ms\n", eventnumber, timeout);
|
||||
break;
|
||||
case exec_cmd:
|
||||
buffer.append(" exec \"");
|
||||
cmdlen = extract<unsigned short>(c);
|
||||
c = StreamProtocolParser::printString(buffer, c);
|
||||
buffer.append("\";\n");
|
||||
break;
|
||||
case connect_cmd:
|
||||
timeout = extract<unsigned long>(c);
|
||||
buffer.printf(" connect %ld; # ms\n", timeout);
|
||||
buffer.print(" connect %ld; # ms\n", timeout);
|
||||
break;
|
||||
case disconnect_cmd:
|
||||
buffer.append(" disconnect;\n");
|
||||
@ -126,7 +118,6 @@ printProtocol()
|
||||
if (onMismatch)
|
||||
printf(" @Mismatch {\n%s }\n",
|
||||
printCommands(buffer.clear(), onMismatch()));
|
||||
debug("StreamCore::printProtocol: commands=%s\n", commands.expand()());
|
||||
printf("\n%s}\n",
|
||||
printCommands(buffer.clear(), commands()));
|
||||
}
|
||||
@ -331,7 +322,7 @@ compileCommand(StreamProtocolParser::Protocol* protocol,
|
||||
{
|
||||
if (!busSupportsEvent())
|
||||
{
|
||||
protocol->errorMsg(getLineNumber(command),
|
||||
error(getLineNumber(command), protocol->filename(),
|
||||
"Events not supported by businterface.\n");
|
||||
return false;
|
||||
}
|
||||
@ -345,7 +336,7 @@ compileCommand(StreamProtocolParser::Protocol* protocol,
|
||||
}
|
||||
if (*args != ')')
|
||||
{
|
||||
protocol->errorMsg(getLineNumber(command),
|
||||
error(getLineNumber(command), protocol->filename(),
|
||||
"Expect ')' instead of: '%s'\n", args);
|
||||
return false;
|
||||
}
|
||||
@ -366,7 +357,7 @@ compileCommand(StreamProtocolParser::Protocol* protocol,
|
||||
{
|
||||
buffer.append(exec_cmd);
|
||||
if (!protocol->compileString(buffer, args,
|
||||
NoFormat, this))
|
||||
PrintFormat, this))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -389,13 +380,29 @@ compileCommand(StreamProtocolParser::Protocol* protocol,
|
||||
return true;
|
||||
}
|
||||
|
||||
protocol->errorMsg(getLineNumber(command),
|
||||
error(getLineNumber(command), protocol->filename(),
|
||||
"Unknown command name '%s'\n", command);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Run the protocol
|
||||
|
||||
// Input and events may come asynchonously from the bus driver
|
||||
// Especially in the sequence 'out "request"; in "reply";' the
|
||||
// reply can come before the 'in' command has actually started.
|
||||
// For asyncronous protocols, input can come at any time.
|
||||
// Thus, we must always accept input and event while the protocol
|
||||
// is running or when asyncronous mode is active.
|
||||
// Early input and event must be buffered until 'in' or 'event'
|
||||
// start. An 'out' command must discard any early input to avoid
|
||||
// problems with late input from aborted protocols.
|
||||
// Async mode ends on Abort or Error or if another command comes
|
||||
// after the asynchronous 'in' command.
|
||||
// Input can be discarded when it is not accepted any more, i.e.
|
||||
// at the end of syncronous protocols and when an asynchronous
|
||||
// mode ends (but not when 'in'-only protocol finishes normally).
|
||||
|
||||
|
||||
bool StreamCore::
|
||||
startProtocol(StartMode startMode)
|
||||
{
|
||||
@ -449,7 +456,7 @@ finishProtocol(ProtocolResult status)
|
||||
flags & WaitPending ? "timerCallback()" : "");
|
||||
status = Fault;
|
||||
}
|
||||
flags &= ~(AcceptInput|AcceptEvent);
|
||||
//// flags &= ~(AcceptInput|AcceptEvent);
|
||||
if (runningHandler)
|
||||
{
|
||||
// get original error status
|
||||
@ -494,6 +501,7 @@ finishProtocol(ProtocolResult status)
|
||||
break;
|
||||
default:
|
||||
// get rid of all the rubbish whe might have collected
|
||||
unparsedInput = false;
|
||||
inputBuffer.clear();
|
||||
handler = NULL;
|
||||
}
|
||||
@ -524,6 +532,7 @@ finishProtocol(ProtocolResult status)
|
||||
flags &= ~BusOwner;
|
||||
}
|
||||
busFinish();
|
||||
flags &= ~(AcceptInput|AcceptEvent);
|
||||
protocolFinishHook(status);
|
||||
}
|
||||
|
||||
@ -545,16 +554,16 @@ evalCommand()
|
||||
switch (*commandIndex++)
|
||||
{
|
||||
case out_cmd:
|
||||
flags &= ~(AcceptInput|AcceptEvent);
|
||||
//// flags &= ~(AcceptInput|AcceptEvent);
|
||||
return evalOut();
|
||||
case in_cmd:
|
||||
flags &= ~AcceptEvent;
|
||||
//// flags &= ~AcceptEvent;
|
||||
return evalIn();
|
||||
case wait_cmd:
|
||||
flags &= ~(AcceptInput|AcceptEvent);
|
||||
//// flags &= ~(AcceptInput|AcceptEvent);
|
||||
return evalWait();
|
||||
case event_cmd:
|
||||
flags &= ~AcceptInput;
|
||||
//// flags &= ~AcceptInput;
|
||||
return evalEvent();
|
||||
case exec_cmd:
|
||||
return evalExec();
|
||||
@ -579,9 +588,9 @@ evalCommand()
|
||||
bool StreamCore::
|
||||
evalOut()
|
||||
{
|
||||
inputBuffer.clear(); // flush all unread input
|
||||
// flush all unread input
|
||||
unparsedInput = false;
|
||||
outputLine.clear();
|
||||
inputBuffer.clear();
|
||||
if (!formatOutput())
|
||||
{
|
||||
finishProtocol(FormatError);
|
||||
@ -622,33 +631,50 @@ formatOutput()
|
||||
char command;
|
||||
const char* fieldName = NULL;
|
||||
const char* formatstring;
|
||||
int formatstringlen;
|
||||
|
||||
outputLine.clear();
|
||||
while ((command = *commandIndex++) != StreamProtocolParser::eos)
|
||||
{
|
||||
switch (command)
|
||||
{
|
||||
case StreamProtocolParser::format_field:
|
||||
{
|
||||
debug("StreamCore::formatOutput(%s): StreamProtocolParser::format_field\n",
|
||||
debug("StreamCore::formatOutput(%s): StreamProtocolParser::redirect_format\n",
|
||||
name());
|
||||
// code layout:
|
||||
// field <StreamProtocolParser::eos> addrlen AddressStructure formatstring <StreamProtocolParser::eos> StreamFormat [info]
|
||||
// field <eos> addrlen AddressStructure formatstring <eos> StreamFormat [info]
|
||||
fieldName = commandIndex;
|
||||
commandIndex += strlen(commandIndex)+1;
|
||||
unsigned short addrlen = extract<unsigned short>(commandIndex);
|
||||
fieldAddress.set(commandIndex, addrlen);
|
||||
commandIndex += addrlen;
|
||||
goto normal_format;
|
||||
}
|
||||
case StreamProtocolParser::format:
|
||||
{
|
||||
fieldAddress.clear();
|
||||
normal_format:
|
||||
// code layout:
|
||||
// formatstring <StreamProtocolParser::eos> StreamFormat [info]
|
||||
// formatstring <eos> StreamFormat [info]
|
||||
formatstring = commandIndex;
|
||||
while (*commandIndex++); // jump after <StreamProtocolParser::eos>
|
||||
// jump after <eos>
|
||||
while (*commandIndex)
|
||||
{
|
||||
if (*commandIndex == esc) commandIndex++;
|
||||
commandIndex++;
|
||||
}
|
||||
formatstringlen = commandIndex-formatstring;
|
||||
commandIndex++;
|
||||
|
||||
StreamFormat fmt = extract<StreamFormat>(commandIndex);
|
||||
fmt.info = commandIndex; // point to info string
|
||||
commandIndex += fmt.infolen;
|
||||
#ifndef NO_TEMPORARY
|
||||
debug("StreamCore::formatOutput(%s): format = %%%s\n",
|
||||
name(), formatstring);
|
||||
name(), StreamBuffer(formatstring, formatstringlen).expand()());
|
||||
#endif
|
||||
|
||||
if (fmt.type == pseudo_format)
|
||||
{
|
||||
if (!StreamFormatConverter::find(fmt.conv)->
|
||||
@ -663,18 +689,21 @@ formatOutput()
|
||||
flags &= ~Separator;
|
||||
if (!formatValue(fmt, fieldAddress ? fieldAddress() : NULL))
|
||||
{
|
||||
if (fieldName)
|
||||
StreamBuffer formatstr(formatstring, formatstringlen);
|
||||
if (fieldAddress)
|
||||
error("%s: Cannot format field '%s' with '%%%s'\n",
|
||||
name(), fieldName, formatstring);
|
||||
name(), fieldName, formatstr.expand()());
|
||||
else
|
||||
error("%s: Cannot format value with '%%%s'\n",
|
||||
name(), formatstring);
|
||||
name(), formatstr.expand()());
|
||||
return false;
|
||||
}
|
||||
fieldAddress.clear();
|
||||
fieldName = NULL;
|
||||
continue;
|
||||
}
|
||||
case StreamProtocolParser::whitespace:
|
||||
outputLine.append(' ');
|
||||
case StreamProtocolParser::skip:
|
||||
continue;
|
||||
case esc:
|
||||
// escaped literal byte
|
||||
command = *commandIndex++;
|
||||
@ -696,12 +725,21 @@ printSeparator()
|
||||
}
|
||||
if (!separator) return;
|
||||
long i = 0;
|
||||
if (separator[0] == ' ') i++; // ignore leading space
|
||||
for (; i < separator.length(); i++)
|
||||
{
|
||||
if (separator[i] == StreamProtocolParser::skip) continue; // wildcard
|
||||
if (separator[i] == esc) i++; // escaped literal byte
|
||||
outputLine.append(separator[i]);
|
||||
switch (separator[i])
|
||||
{
|
||||
case StreamProtocolParser::whitespace:
|
||||
outputLine.append(' '); // print single space
|
||||
case StreamProtocolParser::skip:
|
||||
continue;
|
||||
case esc:
|
||||
// escaped literal byte
|
||||
i++;
|
||||
default:
|
||||
// literal byte
|
||||
outputLine.append(separator[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -722,6 +760,8 @@ printValue(const StreamFormat& fmt, long value)
|
||||
name(), value);
|
||||
return false;
|
||||
}
|
||||
debug("StreamCore::printValue(%s, long): \"%s\"\n",
|
||||
name(), outputLine.expand()());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -742,6 +782,8 @@ printValue(const StreamFormat& fmt, double value)
|
||||
name(), value);
|
||||
return false;
|
||||
}
|
||||
debug("StreamCore::printValue(%s, double): \"%s\"\n",
|
||||
name(), outputLine.expand()());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -763,6 +805,8 @@ printValue(const StreamFormat& fmt, char* value)
|
||||
name(), buffer.expand()());
|
||||
return false;
|
||||
}
|
||||
debug("StreamCore::printValue(%s, char*): \"%s\"\n",
|
||||
name(), outputLine.expand()());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -780,10 +824,16 @@ lockCallback(StreamIoStatus status)
|
||||
}
|
||||
flags &= ~LockPending;
|
||||
flags |= BusOwner;
|
||||
if (status != StreamIoSuccess)
|
||||
switch (status)
|
||||
{
|
||||
finishProtocol(LockTimeout);
|
||||
return;
|
||||
case StreamIoSuccess:
|
||||
break;
|
||||
case StreamIoTimeout:
|
||||
finishProtocol(LockTimeout);
|
||||
return;
|
||||
default:
|
||||
finishProtocol(Fault);
|
||||
return;
|
||||
}
|
||||
flags |= WritePending;
|
||||
if (!busWriteRequest(outputLine(), outputLine.length(), writeTimeout))
|
||||
@ -807,8 +857,6 @@ writeCallback(StreamIoStatus status)
|
||||
flags &= ~WritePending;
|
||||
if (status != StreamIoSuccess)
|
||||
{
|
||||
error("%s: write failed: %s\n",
|
||||
name(), StreamIoStatusStr[status]);
|
||||
finishProtocol(WriteTimeout);
|
||||
return;
|
||||
}
|
||||
@ -894,12 +942,17 @@ readCallback(StreamIoStatus status,
|
||||
|
||||
if (!(flags & AcceptInput))
|
||||
{
|
||||
error("StreamCore::readCallback(%s) called unexpectedly\n",
|
||||
name());
|
||||
#ifdef NO_TEMPORARY
|
||||
error("StreamCore::readCallback(%s, %s) called unexpectedly\n",
|
||||
name(), StreamIoStatusStr[status]);
|
||||
#else
|
||||
error("StreamCore::readCallback(%s, %s, \"%s\") called unexpectedly\n",
|
||||
name(), StreamIoStatusStr[status],
|
||||
StreamBuffer(input, size).expand()());
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
flags &= ~AcceptInput;
|
||||
unparsedInput = false;
|
||||
//// flags &= ~AcceptInput;
|
||||
switch (status)
|
||||
{
|
||||
case StreamIoTimeout:
|
||||
@ -956,10 +1009,36 @@ readCallback(StreamIoStatus status,
|
||||
if (inTerminator)
|
||||
{
|
||||
// look for terminator
|
||||
end = inputBuffer.find(inTerminator);
|
||||
if (end >= 0) termlen = inTerminator.length();
|
||||
debug("StreamCore::readCallback(%s) inTerminator %sfound\n",
|
||||
name(), end >= 0 ? "" : "not ");
|
||||
// performance issue for long inputs that come in chunks:
|
||||
// do not parse old chunks again or performance decreases to O(n^2)
|
||||
// but make sure to get all terminators in multi-line input
|
||||
|
||||
long start;
|
||||
if (unparsedInput)
|
||||
{
|
||||
// multi-line input sets 'unparsedInput' and removes the line
|
||||
// remaining unparsed lines are left at start of inputBuffer
|
||||
start = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// long line does not set 'unparsedInput' but keeps
|
||||
// already parsed chunks in inputBuffer
|
||||
// start parsing at beginning of new data
|
||||
// but beware of split terminators
|
||||
start = inputBuffer.length() - size - inTerminator.length();
|
||||
if (start < 0) start = 0;
|
||||
}
|
||||
end = inputBuffer.find(inTerminator, start);
|
||||
if (end >= 0)
|
||||
{
|
||||
termlen = inTerminator.length();
|
||||
debug("StreamCore::readCallback(%s) inTerminator %s at position %ld\n",
|
||||
name(), inTerminator.expand()(), end);
|
||||
} else {
|
||||
debug("StreamCore::readCallback(%s) inTerminator %s not found\n",
|
||||
name(), inTerminator.expand()());
|
||||
}
|
||||
}
|
||||
if (status == StreamIoEnd && end < 0)
|
||||
{
|
||||
@ -981,6 +1060,12 @@ readCallback(StreamIoStatus status,
|
||||
end = maxInput;
|
||||
termlen = 0;
|
||||
}
|
||||
if (end >= 0)
|
||||
{
|
||||
// be forgiving with timeout because end is found
|
||||
if (status == StreamIoTimeout)
|
||||
status = StreamIoEnd;
|
||||
}
|
||||
if (end < 0)
|
||||
{
|
||||
// no end found
|
||||
@ -997,23 +1082,23 @@ readCallback(StreamIoStatus status,
|
||||
}
|
||||
// try to parse what we got
|
||||
end = inputBuffer.length();
|
||||
if (!(flags & AsyncMode)||showAsyncErrors)
|
||||
if (flags & AsyncMode)
|
||||
{
|
||||
debug("StreamCore::readCallback(%s) async timeout: just restart\n",
|
||||
name());
|
||||
unparsedInput = false;
|
||||
inputBuffer.clear();
|
||||
commandIndex = commandStart;
|
||||
evalIn();
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
error("%s: Timeout after reading %ld byte%s \"%s%s\"\n",
|
||||
name(), end, end==1 ? "" : "s", end > 20 ? "..." : "",
|
||||
inputBuffer.expand(-20)());
|
||||
}
|
||||
}
|
||||
|
||||
if (status == StreamIoTimeout && (flags & AsyncMode))
|
||||
{
|
||||
debug("StreamCore::readCallback(%s) async timeout: just restart\n",
|
||||
name());
|
||||
inputBuffer.clear();
|
||||
commandIndex = commandStart;
|
||||
evalIn();
|
||||
return 0;
|
||||
}
|
||||
|
||||
inputLine.set(inputBuffer(), end);
|
||||
debug("StreamCore::readCallback(%s) input line: \"%s\"\n",
|
||||
@ -1026,6 +1111,10 @@ readCallback(StreamIoStatus status,
|
||||
name(), inputBuffer.expand()());
|
||||
unparsedInput = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
unparsedInput = false;
|
||||
}
|
||||
if (!matches)
|
||||
{
|
||||
if (status == StreamIoTimeout)
|
||||
@ -1054,7 +1143,7 @@ readCallback(StreamIoStatus status,
|
||||
return 0;
|
||||
}
|
||||
// end input mode and do next command
|
||||
flags &= ~(AsyncMode|AcceptInput);
|
||||
//// flags &= ~(AsyncMode|AcceptInput);
|
||||
// -- should we tell someone that input has finished? --
|
||||
evalCommand();
|
||||
return 0;
|
||||
@ -1068,7 +1157,8 @@ matchInput()
|
||||
is installed and starts with 'in' (then we reparse the input).
|
||||
*/
|
||||
char command;
|
||||
const char* formatstring;
|
||||
const char* fieldName = NULL;
|
||||
StreamBuffer formatstring;
|
||||
|
||||
consumedInput = 0;
|
||||
|
||||
@ -1080,22 +1170,30 @@ matchInput()
|
||||
{
|
||||
// code layout:
|
||||
// field <StreamProtocolParser::eos> addrlen AddressStructure formatstring <StreamProtocolParser::eos> StreamFormat [info]
|
||||
fieldName = commandIndex;
|
||||
commandIndex += strlen(commandIndex)+1;
|
||||
unsigned short addrlen = extract<unsigned short>(commandIndex);
|
||||
fieldAddress.set(commandIndex, addrlen);
|
||||
commandIndex += addrlen;
|
||||
goto normal_format;
|
||||
}
|
||||
case StreamProtocolParser::format:
|
||||
{
|
||||
fieldAddress.clear();
|
||||
normal_format:
|
||||
int consumed;
|
||||
// code layout:
|
||||
// formatstring <eos> StreamFormat [info]
|
||||
formatstring = commandIndex;
|
||||
while (*commandIndex++ != StreamProtocolParser::eos); // jump after <eos>
|
||||
commandIndex = StreamProtocolParser::printString(formatstring, commandIndex);
|
||||
|
||||
StreamFormat fmt = extract<StreamFormat>(commandIndex);
|
||||
fmt.info = commandIndex;
|
||||
fmt.info = commandIndex; // point to info string
|
||||
commandIndex += fmt.infolen;
|
||||
#ifndef NO_TEMPORARY
|
||||
debug("StreamCore::matchInput(%s): format = \"%%%s\"\n",
|
||||
name(), formatstring());
|
||||
#endif
|
||||
|
||||
if (fmt.flags & skip_flag || fmt.type == pseudo_format)
|
||||
{
|
||||
long ldummy;
|
||||
@ -1127,42 +1225,100 @@ matchInput()
|
||||
}
|
||||
if (consumed < 0)
|
||||
{
|
||||
if ((!(flags & AsyncMode)||showAsyncErrors) && onMismatch[0] != in_cmd)
|
||||
if (fmt.flags & default_flag)
|
||||
{
|
||||
error("%s: Input \"%s%s\" does not match format %%%s\n",
|
||||
name(), inputLine.expand(consumedInput, 20)(),
|
||||
inputLine.length()-consumedInput > 20 ? "..." : "",
|
||||
formatstring);
|
||||
consumed = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(flags & AsyncMode) && onMismatch[0] != in_cmd)
|
||||
{
|
||||
error("%s: Input \"%s%s\" does not match format \"%%%s\"\n",
|
||||
name(), inputLine.expand(consumedInput, 20)(),
|
||||
inputLine.length()-consumedInput > 20 ? "..." : "",
|
||||
formatstring());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
consumedInput += consumed;
|
||||
break;
|
||||
}
|
||||
if (fmt.flags & compare_flag)
|
||||
{
|
||||
outputLine.clear();
|
||||
flags &= ~Separator;
|
||||
if (!formatValue(fmt, fieldAddress ? fieldAddress() : NULL))
|
||||
{
|
||||
if (fieldAddress)
|
||||
error("%s: Cannot format variable \"%s\" with \"%%%s\"\n",
|
||||
name(), fieldName, formatstring());
|
||||
else
|
||||
error("%s: Cannot format value with \"%%%s\"\n",
|
||||
name(), formatstring());
|
||||
return false;
|
||||
}
|
||||
#ifndef NO_TEMPORARY
|
||||
debug("StreamCore::matchInput(%s): compare \"%s\" with \"%s\"\n",
|
||||
name(), inputLine.expand(consumedInput,
|
||||
outputLine.length())(), outputLine.expand()());
|
||||
#endif
|
||||
if (inputLine.length() - consumedInput < outputLine.length())
|
||||
{
|
||||
if (!(flags & AsyncMode) && onMismatch[0] != in_cmd)
|
||||
{
|
||||
error("%s: Input \"%s%s\" too short."
|
||||
" No match for format \"%%%s\" (\"%s\")\n",
|
||||
name(),
|
||||
inputLine.length() > 20 ? "..." : "",
|
||||
inputLine.expand(-20)(),
|
||||
formatstring(),
|
||||
outputLine.expand()());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
consumedInput += consumed;
|
||||
if (!outputLine.startswith(inputLine(consumedInput),outputLine.length()))
|
||||
{
|
||||
if (!(flags & AsyncMode) && onMismatch[0] != in_cmd)
|
||||
{
|
||||
error("%s: Input \"%s%s\" does not match format \"%%%s\" (\"%s\")\n",
|
||||
name(), inputLine.expand(consumedInput, 20)(),
|
||||
inputLine.length()-consumedInput > 20 ? "..." : "",
|
||||
formatstring(),
|
||||
outputLine.expand()());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
consumedInput += outputLine.length();
|
||||
break;
|
||||
}
|
||||
flags &= ~Separator;
|
||||
if (!matchValue(fmt, fieldAddress ? fieldAddress() : NULL))
|
||||
{
|
||||
if ((!(flags & AsyncMode)||showAsyncErrors) && onMismatch[0] != in_cmd)
|
||||
if (!(flags & AsyncMode) && onMismatch[0] != in_cmd)
|
||||
{
|
||||
if (flags & ScanTried)
|
||||
error("%s: Input \"%s%s\" does not match format %%%s\n",
|
||||
error("%s: Input \"%s%s\" does not match format \"%%%s\"\n",
|
||||
name(), inputLine.expand(consumedInput, 20)(),
|
||||
inputLine.length()-consumedInput > 20 ? "..." : "",
|
||||
formatstring);
|
||||
formatstring());
|
||||
else
|
||||
error("%s: Can't scan value with format %%%s\n",
|
||||
name(), formatstring);
|
||||
error("%s: Format \"%%%s\" has data type %s which does not match variable \"%s\".\n",
|
||||
name(), formatstring(), StreamFormatTypeStr[fmt.type], fieldName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// matchValue() has already removed consumed bytes from inputBuffer
|
||||
fieldAddress = NULL;
|
||||
break;
|
||||
}
|
||||
case StreamProtocolParser::skip:
|
||||
// ignore next input byte
|
||||
consumedInput++;
|
||||
break;
|
||||
case StreamProtocolParser::whitespace:
|
||||
// any number of whitespace (including 0)
|
||||
while (isspace(inputLine[consumedInput])) consumedInput++;
|
||||
break;
|
||||
case esc:
|
||||
// escaped literal byte
|
||||
command = *commandIndex++;
|
||||
@ -1172,20 +1328,22 @@ matchInput()
|
||||
{
|
||||
int i = 0;
|
||||
while (commandIndex[i] >= ' ') i++;
|
||||
if ((!(flags & AsyncMode)||showAsyncErrors) && onMismatch[0] != in_cmd)
|
||||
if (!(flags & AsyncMode) && onMismatch[0] != in_cmd)
|
||||
{
|
||||
error("%s: Input \"%s%s\" too short."
|
||||
" No match for \"%s\"\n",
|
||||
error("%s: Input \"%s%s\" too short.\n",
|
||||
name(),
|
||||
inputLine.length() > 20 ? "..." : "",
|
||||
inputLine.expand(-20)(),
|
||||
inputLine.expand(-20)());
|
||||
#ifndef NO_TEMPORARY
|
||||
error("No match for \"%s\"\n",
|
||||
StreamBuffer(commandIndex-1,i+1).expand()());
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (command != inputLine[consumedInput])
|
||||
{
|
||||
if ((!(flags & AsyncMode)||showAsyncErrors) && onMismatch[0] != in_cmd)
|
||||
if (!(flags & AsyncMode) && onMismatch[0] != in_cmd)
|
||||
{
|
||||
int i = 0;
|
||||
while (commandIndex[i] >= ' ') i++;
|
||||
@ -1197,10 +1355,12 @@ matchInput()
|
||||
consumedInput,
|
||||
consumedInput==1 ? "" : "s");
|
||||
|
||||
#ifndef NO_TEMPORARY
|
||||
error("%s: got \"%s\" where \"%s\" was expected\n",
|
||||
name(),
|
||||
inputLine.expand(consumedInput, 20)(),
|
||||
StreamBuffer(commandIndex-1,i+1).expand()());
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -1210,7 +1370,7 @@ matchInput()
|
||||
long surplus = inputLine.length()-consumedInput;
|
||||
if (surplus > 0 && !(flags & IgnoreExtraInput))
|
||||
{
|
||||
if ((!(flags & AsyncMode)||showAsyncErrors) && onMismatch[0] != in_cmd)
|
||||
if (!(flags & AsyncMode) && onMismatch[0] != in_cmd)
|
||||
{
|
||||
error("%s: %ld byte%s surplus input \"%s%s\"\n",
|
||||
name(), surplus, surplus==1 ? "" : "s",
|
||||
@ -1236,36 +1396,45 @@ matchInput()
|
||||
bool StreamCore::
|
||||
matchSeparator()
|
||||
{
|
||||
// called before value is read, first value has Separator flag cleared
|
||||
// for second and next value set Separator flag
|
||||
|
||||
if (!separator) {
|
||||
// empty separator matches
|
||||
return true;
|
||||
}
|
||||
if (!(flags & Separator))
|
||||
{
|
||||
// before first element, don't expect separator yet
|
||||
flags |= Separator;
|
||||
return true;
|
||||
}
|
||||
if (!separator) return true;
|
||||
long i = 0;
|
||||
if (separator[0] == ' ')
|
||||
long i;
|
||||
long j = consumedInput;
|
||||
for (i = 0; i < separator.length(); i++)
|
||||
{
|
||||
i++;
|
||||
// skip leading whitespaces
|
||||
while (isspace(inputLine[consumedInput++]));
|
||||
switch (separator[i])
|
||||
{
|
||||
case StreamProtocolParser::skip:
|
||||
j++;
|
||||
continue;
|
||||
case StreamProtocolParser::whitespace:
|
||||
while (isspace(inputLine[j])) j++;
|
||||
continue;
|
||||
case esc:
|
||||
i++;
|
||||
default:
|
||||
if (separator[i] != inputLine[j])
|
||||
{
|
||||
// no match
|
||||
// don't complain here, just return false
|
||||
return false;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
}
|
||||
for (; i < separator.length(); i++,consumedInput++)
|
||||
{
|
||||
if (!inputLine[consumedInput]) return false;
|
||||
if (separator[i] == StreamProtocolParser::skip) continue; // wildcard
|
||||
if (separator[i] == esc) i++; // escaped literal byte
|
||||
if (separator[i] != inputLine[consumedInput]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StreamCore::
|
||||
scanSeparator()
|
||||
{
|
||||
// for compatibility only
|
||||
// read and remove separator
|
||||
if (!matchSeparator()) return false;
|
||||
flags &= ~Separator;
|
||||
// separator successfully read
|
||||
consumedInput = j;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1284,8 +1453,16 @@ scanValue(const StreamFormat& fmt, long& value)
|
||||
scanLong(fmt, inputLine(consumedInput), value);
|
||||
debug("StreamCore::scanValue(%s, format=%%%c, long) input=\"%s\"\n",
|
||||
name(), fmt.conv, inputLine.expand(consumedInput)());
|
||||
if (consumed < 0 ||
|
||||
consumed > inputLine.length()-consumedInput) return -1;
|
||||
if (consumed < 0)
|
||||
{
|
||||
if (fmt.flags & default_flag)
|
||||
{
|
||||
value = 0;
|
||||
consumed = 0;
|
||||
}
|
||||
else return -1;
|
||||
}
|
||||
if (consumed > inputLine.length()-consumedInput) return -1;
|
||||
debug("StreamCore::scanValue(%s) scanned %li\n",
|
||||
name(), value);
|
||||
flags |= GotValue;
|
||||
@ -1306,9 +1483,17 @@ scanValue(const StreamFormat& fmt, double& value)
|
||||
long consumed = StreamFormatConverter::find(fmt.conv)->
|
||||
scanDouble(fmt, inputLine(consumedInput), value);
|
||||
debug("StreamCore::scanValue(%s, format=%%%c, double) input=\"%s\"\n",
|
||||
name(), fmt.conv, inputLine.expand(consumedInput)());
|
||||
if (consumed < 0 ||
|
||||
consumed > inputLine.length()-consumedInput) return -1;
|
||||
name(), fmt.conv, inputLine.expand(consumedInput, 20)());
|
||||
if (consumed < 0)
|
||||
{
|
||||
if (fmt.flags & default_flag)
|
||||
{
|
||||
value = 0.0;
|
||||
consumed = 0;
|
||||
}
|
||||
else return -1;
|
||||
}
|
||||
if (consumed > inputLine.length()-consumedInput) return -1;
|
||||
debug("StreamCore::scanValue(%s) scanned %#g\n",
|
||||
name(), value);
|
||||
flags |= GotValue;
|
||||
@ -1331,8 +1516,16 @@ scanValue(const StreamFormat& fmt, char* value, long maxlen)
|
||||
scanString(fmt, inputLine(consumedInput), value, maxlen);
|
||||
debug("StreamCore::scanValue(%s, format=%%%c, char*, maxlen=%ld) input=\"%s\"\n",
|
||||
name(), fmt.conv, maxlen, inputLine.expand(consumedInput)());
|
||||
if (consumed < 0 ||
|
||||
consumed > inputLine.length()-consumedInput) return -1;
|
||||
if (consumed < 0)
|
||||
{
|
||||
if (fmt.flags & default_flag)
|
||||
{
|
||||
value[0] = 0;
|
||||
consumed = 0;
|
||||
}
|
||||
else return -1;
|
||||
}
|
||||
if (consumed > inputLine.length()-consumedInput) return -1;
|
||||
#ifndef NO_TEMPORARY
|
||||
debug("StreamCore::scanValue(%s) scanned \"%s\"\n",
|
||||
name(), StreamBuffer(value, maxlen).expand()());
|
||||
@ -1444,8 +1637,8 @@ timerCallback()
|
||||
bool StreamCore::
|
||||
evalExec()
|
||||
{
|
||||
outputLine.clear();
|
||||
formatOutput();
|
||||
debug ("StreamCore::evalExec: command = \"%s\"\n", outputLine.expand()());
|
||||
// release bus
|
||||
if (flags & BusOwner)
|
||||
{
|
||||
|
@ -139,7 +139,6 @@ protected:
|
||||
long scanValue(const StreamFormat& format, double& value);
|
||||
long scanValue(const StreamFormat& format, char* value, long maxlen);
|
||||
long scanValue(const StreamFormat& format);
|
||||
bool scanSeparator(); // disencouraged
|
||||
|
||||
StreamBuffer protocolname;
|
||||
unsigned long lockTimeout;
|
||||
|
@ -18,11 +18,16 @@
|
||||
* *
|
||||
***************************************************************/
|
||||
|
||||
#include "devStream.h"
|
||||
#include "StreamCore.h"
|
||||
#include "StreamError.h"
|
||||
#include "devStream.h"
|
||||
|
||||
#ifndef EPICS_3_14
|
||||
#include <epicsVersion.h>
|
||||
#ifdef BASE_VERSION
|
||||
#define EPICS_3_13
|
||||
#endif
|
||||
|
||||
#ifdef EPICS_3_13
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
@ -34,13 +39,15 @@ extern "C" {
|
||||
#include <recSup.h>
|
||||
#include <recGbl.h>
|
||||
#include <devLib.h>
|
||||
#define epicsAlarmGLOBAL
|
||||
#include <alarm.h>
|
||||
#include <callback.h>
|
||||
|
||||
#ifndef EPICS_3_14
|
||||
#ifdef EPICS_3_13
|
||||
|
||||
#include <semLib.h>
|
||||
#include <wdLib.h>
|
||||
#include <taskLib.h>
|
||||
|
||||
extern DBBASE *pdbbase;
|
||||
|
||||
@ -52,6 +59,7 @@ extern DBBASE *pdbbase;
|
||||
#include <epicsMutex.h>
|
||||
#include <epicsEvent.h>
|
||||
#include <epicsTime.h>
|
||||
#include <epicsThread.h>
|
||||
#include <registryFunction.h>
|
||||
#include <iocsh.h>
|
||||
|
||||
@ -76,31 +84,33 @@ epicsShareFunc int epicsShareAPI iocshCmd(const char *command);
|
||||
enum MoreFlags {
|
||||
// 0x00FFFFFF used by StreamCore
|
||||
InDestructor = 0x0100000,
|
||||
ValueReceived = 0x0200000
|
||||
ValueReceived = 0x0200000,
|
||||
Aborted = 0x0400000
|
||||
};
|
||||
|
||||
extern "C" void streamExecuteCommand(CALLBACK *pcallback);
|
||||
extern "C" void streamRecordProcessCallback(CALLBACK *pcallback);
|
||||
extern "C" long streamReload(char* recordname);
|
||||
|
||||
class Stream : protected StreamCore
|
||||
#ifdef EPICS_3_14
|
||||
#ifndef EPICS_3_13
|
||||
, epicsTimerNotify
|
||||
#endif
|
||||
{
|
||||
dbCommon* record;
|
||||
struct link *ioLink;
|
||||
const struct link *ioLink;
|
||||
streamIoFunction readData;
|
||||
streamIoFunction writeData;
|
||||
#ifdef EPICS_3_14
|
||||
epicsTimerQueueActive* timerQueue;
|
||||
epicsTimer* timer;
|
||||
epicsMutex mutex;
|
||||
epicsEvent initDone;
|
||||
#else
|
||||
#ifdef EPICS_3_13
|
||||
WDOG_ID timer;
|
||||
CALLBACK timeoutCallback;
|
||||
SEM_ID mutex;
|
||||
SEM_ID initDone;
|
||||
#else
|
||||
epicsTimerQueueActive* timerQueue;
|
||||
epicsTimer* timer;
|
||||
epicsMutex mutex;
|
||||
epicsEvent initDone;
|
||||
#endif
|
||||
StreamBuffer fieldBuffer;
|
||||
int status;
|
||||
@ -108,17 +118,18 @@ class Stream : protected StreamCore
|
||||
long currentValueLength;
|
||||
IOSCANPVT ioscanpvt;
|
||||
CALLBACK commandCallback;
|
||||
CALLBACK processCallback;
|
||||
|
||||
|
||||
#ifdef EPICS_3_14
|
||||
#ifdef EPICS_3_13
|
||||
static void expire(CALLBACK *pcallback);
|
||||
#else
|
||||
// epicsTimerNotify method
|
||||
expireStatus expire(const epicsTime&);
|
||||
#else
|
||||
static void expire(CALLBACK *pcallback);
|
||||
#endif
|
||||
|
||||
// StreamCore methods
|
||||
// void protocolStartHook(); // Nothing to do here?
|
||||
void protocolStartHook();
|
||||
void protocolFinishHook(ProtocolResult);
|
||||
void startTimer(unsigned long timeout);
|
||||
bool getFieldAddress(const char* fieldname,
|
||||
@ -131,18 +142,22 @@ class Stream : protected StreamCore
|
||||
void releaseMutex();
|
||||
bool execute();
|
||||
friend void streamExecuteCommand(CALLBACK *pcallback);
|
||||
friend void streamRecordProcessCallback(CALLBACK *pcallback);
|
||||
|
||||
// Stream Epics methods
|
||||
long initRecord();
|
||||
Stream(dbCommon* record, struct link *ioLink,
|
||||
Stream(dbCommon* record, const struct link *ioLink,
|
||||
streamIoFunction readData, streamIoFunction writeData);
|
||||
~Stream();
|
||||
long parseLink(const struct link *ioLink, char* filename, char* protocol,
|
||||
char* busname, int* addr, char* busparam);
|
||||
long initRecord(const char* filename, const char* protocol,
|
||||
const char* busname, int addr, const char* busparam);
|
||||
bool print(format_t *format, va_list ap);
|
||||
bool scan(format_t *format, void* pvalue, size_t maxStringSize);
|
||||
bool process();
|
||||
|
||||
// device support functions
|
||||
friend long streamInitRecord(dbCommon *record, struct link *ioLink,
|
||||
friend long streamInitRecord(dbCommon *record, const struct link *ioLink,
|
||||
streamIoFunction readData, streamIoFunction writeData);
|
||||
friend long streamReadWrite(dbCommon *record);
|
||||
friend long streamGetIointInfo(int cmd, dbCommon *record,
|
||||
@ -150,7 +165,6 @@ class Stream : protected StreamCore
|
||||
friend long streamPrintf(dbCommon *record, format_t *format, ...);
|
||||
friend long streamScanfN(dbCommon *record, format_t *format,
|
||||
void*, size_t maxStringSize);
|
||||
friend long streamScanSep(dbCommon *record);
|
||||
friend long streamReload(char* recordname);
|
||||
|
||||
public:
|
||||
@ -161,22 +175,12 @@ public:
|
||||
|
||||
|
||||
// shell functions ///////////////////////////////////////////////////////
|
||||
#ifdef EPICS_3_14
|
||||
#ifndef EPICS_3_13
|
||||
extern "C" {
|
||||
epicsExportAddress(int, streamDebug);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MEMGUARD
|
||||
static const iocshFuncDef memguardReportDef =
|
||||
{ "memguardReport", 0, NULL };
|
||||
|
||||
static void memguardReportFunc (const iocshArgBuf *args)
|
||||
{
|
||||
memguardReport();
|
||||
}
|
||||
#endif
|
||||
|
||||
// for subroutine record
|
||||
extern "C" long streamReloadSub()
|
||||
{
|
||||
@ -230,7 +234,7 @@ extern "C" long streamReload(char* recordname)
|
||||
return OK;
|
||||
}
|
||||
|
||||
#ifdef EPICS_3_14
|
||||
#ifndef EPICS_3_13
|
||||
static const iocshArg streamReloadArg0 =
|
||||
{ "recordname", iocshArgString };
|
||||
static const iocshArg * const streamReloadArgs[] =
|
||||
@ -245,9 +249,6 @@ extern "C" void streamReloadFunc (const iocshArgBuf *args)
|
||||
|
||||
static void streamRegistrar ()
|
||||
{
|
||||
#ifdef MEMGUARD
|
||||
iocshRegister(&memguardReportDef, memguardReportFunc);
|
||||
#endif
|
||||
iocshRegister(&reloadDef, streamReloadFunc);
|
||||
// make streamReload available for subroutine records
|
||||
registryFunctionAdd("streamReload",
|
||||
@ -259,7 +260,7 @@ static void streamRegistrar ()
|
||||
extern "C" {
|
||||
epicsExportRegistrar(streamRegistrar);
|
||||
}
|
||||
#endif // EPICS_3_14
|
||||
#endif
|
||||
|
||||
// driver support ////////////////////////////////////////////////////////
|
||||
|
||||
@ -273,19 +274,16 @@ struct stream_drvsup {
|
||||
Stream::drvInit
|
||||
};
|
||||
|
||||
#ifdef EPICS_3_14
|
||||
#ifndef EPICS_3_13
|
||||
extern "C" {
|
||||
epicsExportAddress(drvet, stream);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef EPICS_3_13
|
||||
void streamEpicsPrintTimestamp(char* buffer, int size)
|
||||
{
|
||||
epicsTime tm = epicsTime::getCurrent();
|
||||
tm.strftime(buffer, size, "%Y/%m/%d %H:%M:%S.%03f");
|
||||
}
|
||||
#else
|
||||
void streamEpicsPrintTimestamp(char* buffer, int size)
|
||||
{
|
||||
int tlen;
|
||||
char* c;
|
||||
TS_STAMP tm;
|
||||
tsLocalTime (&tm);
|
||||
@ -294,6 +292,16 @@ void streamEpicsPrintTimestamp(char* buffer, int size)
|
||||
if (c) {
|
||||
c[4] = 0;
|
||||
}
|
||||
tlen = strlen(buffer);
|
||||
sprintf(buffer+tlen, " %.*s", size-tlen-2, taskName(0));
|
||||
}
|
||||
#else
|
||||
void streamEpicsPrintTimestamp(char* buffer, int size)
|
||||
{
|
||||
int tlen;
|
||||
epicsTime tm = epicsTime::getCurrent();
|
||||
tlen = tm.strftime(buffer, size, "%Y/%m/%d %H:%M:%S.%06f");
|
||||
sprintf(buffer+tlen, " %.*s", size-tlen-2, epicsThreadGetNameSelf());
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -310,7 +318,7 @@ report(int interest)
|
||||
printf(" %s\n", interface.name());
|
||||
++interface;
|
||||
}
|
||||
|
||||
|
||||
if (interest < 1) return OK;
|
||||
printf(" registered converters:\n");
|
||||
StreamFormatConverter* converter;
|
||||
@ -323,7 +331,7 @@ report(int interest)
|
||||
printf(" %%%c %s\n", c, converter->name());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Stream* pstream;
|
||||
printf(" connected records:\n");
|
||||
for (pstream = static_cast<Stream*>(first); pstream;
|
||||
@ -341,7 +349,6 @@ report(int interest)
|
||||
pstream->ioLink->value.instio.string);
|
||||
}
|
||||
}
|
||||
StreamPrintTimestampFunction = streamEpicsPrintTimestamp;
|
||||
return OK;
|
||||
}
|
||||
|
||||
@ -397,9 +404,16 @@ long streamInit(int after)
|
||||
return OK;
|
||||
}
|
||||
|
||||
long streamInitRecord(dbCommon* record, struct link *ioLink,
|
||||
long streamInitRecord(dbCommon* record, const struct link *ioLink,
|
||||
streamIoFunction readData, streamIoFunction writeData)
|
||||
{
|
||||
char filename[80];
|
||||
char protocol[80];
|
||||
char busname[80];
|
||||
int addr = -1;
|
||||
char busparam[80];
|
||||
memset(busparam, 0 ,sizeof(busparam));
|
||||
|
||||
debug("streamInitRecord(%s): SEVR=%d\n", record->name, record->sevr);
|
||||
Stream* pstream = (Stream*)record->dpvt;
|
||||
if (!pstream)
|
||||
@ -411,8 +425,12 @@ long streamInitRecord(dbCommon* record, struct link *ioLink,
|
||||
// stop any running protocol
|
||||
pstream->finishProtocol(Stream::Abort);
|
||||
}
|
||||
// scan the i/o link
|
||||
pstream->parseLink(ioLink, filename, protocol,
|
||||
busname, &addr, busparam);
|
||||
// (re)initialize bus and protocol
|
||||
long status = pstream->initRecord();
|
||||
long status = pstream->initRecord(filename, protocol,
|
||||
busname, addr, busparam);
|
||||
if (status != OK && status != DO_NOT_CONVERT)
|
||||
{
|
||||
error("%s: Record initialization failed\n", record->name);
|
||||
@ -440,7 +458,8 @@ long streamGetIointInfo(int cmd, dbCommon *record, IOSCANPVT *ppvt)
|
||||
{
|
||||
Stream* pstream = (Stream*)record->dpvt;
|
||||
debug("streamGetIointInfo(%s,cmd=%d): pstream=%p, ioscanpvt=%p\n",
|
||||
record->name, cmd, (void*)pstream, pstream ? pstream->ioscanpvt : NULL);
|
||||
record->name, cmd,
|
||||
(void*)pstream, pstream ? pstream->ioscanpvt : NULL);
|
||||
if (!pstream)
|
||||
{
|
||||
error("streamGetIointInfo called without stream instance\n");
|
||||
@ -478,20 +497,11 @@ long streamPrintf(dbCommon *record, format_t *format, ...)
|
||||
return success ? OK : ERROR;
|
||||
}
|
||||
|
||||
long streamScanSep(dbCommon* record)
|
||||
{
|
||||
// depreciated
|
||||
debug("streamScanSep(%s)\n", record->name);
|
||||
Stream* pstream = (Stream*)record->dpvt;
|
||||
if (!pstream) return ERROR;
|
||||
return pstream->scanSeparator() ? OK : ERROR;
|
||||
}
|
||||
|
||||
long streamScanfN(dbCommon* record, format_t *format,
|
||||
void* value, size_t maxStringSize)
|
||||
{
|
||||
debug("streamScanfN(%s,format=%%%c,maxStringSize=%d)\n",
|
||||
record->name, format->priv->conv, maxStringSize);
|
||||
debug("streamScanfN(%s,format=%%%c,maxStringSize=%ld)\n",
|
||||
record->name, format->priv->conv, (long)maxStringSize);
|
||||
Stream* pstream = (Stream*)record->dpvt;
|
||||
if (!pstream) return ERROR;
|
||||
if (!pstream->scan(format, value, maxStringSize))
|
||||
@ -508,23 +518,25 @@ long streamScanfN(dbCommon* record, format_t *format,
|
||||
// Stream methods ////////////////////////////////////////////////////////
|
||||
|
||||
Stream::
|
||||
Stream(dbCommon* _record, struct link *ioLink,
|
||||
Stream(dbCommon* _record, const struct link *ioLink,
|
||||
streamIoFunction readData, streamIoFunction writeData)
|
||||
:record(_record), ioLink(ioLink), readData(readData), writeData(writeData)
|
||||
{
|
||||
streamname = record->name;
|
||||
#ifdef EPICS_3_14
|
||||
timerQueue = &epicsTimerQueueActive::allocate(true);
|
||||
timer = &timerQueue->createTimer();
|
||||
#else
|
||||
#ifdef EPICS_3_13
|
||||
timer = wdCreate();
|
||||
mutex = semMCreate(SEM_INVERSION_SAFE | SEM_Q_PRIORITY);
|
||||
initDone = semBCreate(SEM_Q_FIFO, SEM_EMPTY);
|
||||
callbackSetCallback(expire, &timeoutCallback);
|
||||
callbackSetUser(this, &timeoutCallback);
|
||||
#else
|
||||
timerQueue = &epicsTimerQueueActive::allocate(true);
|
||||
timer = &timerQueue->createTimer();
|
||||
#endif
|
||||
callbackSetCallback(streamExecuteCommand, &commandCallback);
|
||||
callbackSetUser(this, &commandCallback);
|
||||
callbackSetCallback(streamRecordProcessCallback, &processCallback);
|
||||
callbackSetUser(this, &processCallback);
|
||||
status = ERROR;
|
||||
convert = DO_NOT_CONVERT;
|
||||
ioscanpvt = NULL;
|
||||
@ -543,57 +555,57 @@ Stream::
|
||||
record->dpvt = NULL;
|
||||
debug("~Stream(%s): dpvt cleared\n", name());
|
||||
}
|
||||
#ifdef EPICS_3_14
|
||||
#ifdef EPICS_3_13
|
||||
wdDelete(timer);
|
||||
debug("~Stream(%s): watchdog destroyed\n", name());
|
||||
#else
|
||||
timer->destroy();
|
||||
debug("~Stream(%s): timer destroyed\n", name());
|
||||
timerQueue->release();
|
||||
debug("~Stream(%s): timer queue released\n", name());
|
||||
#else
|
||||
wdDelete(timer);
|
||||
debug("~Stream(%s): watchdog destroyed\n", name());
|
||||
#endif
|
||||
releaseMutex();
|
||||
}
|
||||
|
||||
long Stream::
|
||||
initRecord()
|
||||
parseLink(const struct link *ioLink, char* filename,
|
||||
char* protocol, char* busname, int* addr, char* busparam)
|
||||
{
|
||||
// scan link parameters: filename protocol busname addr busparam
|
||||
// It is safe to call this function again with different
|
||||
// link text or different protocol file.
|
||||
|
||||
char filename[80];
|
||||
char protocol[80];
|
||||
char busname[80];
|
||||
int addr = -1;
|
||||
char busparam[80];
|
||||
// parse link parameters: filename protocol busname addr busparam
|
||||
int n;
|
||||
|
||||
if (ioLink->type != INST_IO)
|
||||
{
|
||||
error("%s: Wrong link type %s\n", name(),
|
||||
error("%s: Wrong I/O link type %s\n", name(),
|
||||
pamaplinkType[ioLink->type].strvalue);
|
||||
return S_dev_badInitRet;
|
||||
}
|
||||
int items = sscanf(ioLink->value.instio.string, "%79s%79s%79s%n%i%n",
|
||||
filename, protocol, busname, &n, &addr, &n);
|
||||
int items = sscanf(ioLink->value.instio.string, "%s%s%s%n%i%n",
|
||||
filename, protocol, busname, &n, addr, &n);
|
||||
if (items <= 0)
|
||||
{
|
||||
error("%s: Empty link. Forgot the leading '@' or confused INP with OUT ?\n",
|
||||
error("%s: Empty I/O link. "
|
||||
"Forgot the leading '@' or confused INP with OUT ?\n",
|
||||
name());
|
||||
return S_dev_badInitRet;
|
||||
}
|
||||
if (items < 3)
|
||||
{
|
||||
error("%s: Wrong link format\n"
|
||||
error("%s: Wrong I/O link format\n"
|
||||
" expect \"@file protocol bus addr params\"\n"
|
||||
" in \"@%s\"\n", name(),
|
||||
ioLink->value.instio.string);
|
||||
return S_dev_badInitRet;
|
||||
}
|
||||
memset(busparam, 0 ,80);
|
||||
for (n = 0; isspace((unsigned char)ioLink->value.instio.string[n]); n++);
|
||||
strncpy (busparam, ioLink->value.constantStr+n, 79);
|
||||
while (isspace((unsigned char)ioLink->value.instio.string[n])) n++;
|
||||
strcpy (busparam, ioLink->value.constantStr+n);
|
||||
return OK;
|
||||
}
|
||||
|
||||
long Stream::
|
||||
initRecord(const char* filename, const char* protocol,
|
||||
const char* busname, int addr, const char* busparam)
|
||||
{
|
||||
// It is safe to call this function again with different arguments
|
||||
|
||||
// attach to bus interface
|
||||
if (!attachBus(busname, addr, busparam))
|
||||
@ -621,7 +633,8 @@ initRecord()
|
||||
name());
|
||||
if (record->scan == SCAN_IO_EVENT)
|
||||
{
|
||||
debug("Stream::initRecord %s: restarting \"I/O Intr\" after reload\n",
|
||||
debug("Stream::initRecord %s: "
|
||||
"restarting \"I/O Intr\" after reload\n",
|
||||
name());
|
||||
if (!startProtocol(StartAsync))
|
||||
{
|
||||
@ -646,10 +659,10 @@ initRecord()
|
||||
}
|
||||
debug("Stream::initRecord %s: waiting for initDone\n",
|
||||
name());
|
||||
#ifdef EPICS_3_14
|
||||
initDone.wait();
|
||||
#else
|
||||
#ifdef EPICS_3_13
|
||||
semTake(initDone, WAIT_FOREVER);
|
||||
#else
|
||||
initDone.wait();
|
||||
#endif
|
||||
debug("Stream::initRecord %s: initDone\n",
|
||||
name());
|
||||
@ -675,8 +688,11 @@ process()
|
||||
{
|
||||
if (status != NO_ALARM)
|
||||
{
|
||||
debug("Stream::process(%s) error status=%d\n",
|
||||
name(), status);
|
||||
debug("Stream::process(%s) error status=%s (%d)\n",
|
||||
name(),
|
||||
status >= 0 && status < ALARM_NSTATUS ?
|
||||
epicsAlarmConditionStrings[status] : "ERROR",
|
||||
status);
|
||||
(void) recGblSetSevr(record, status, INVALID_ALARM);
|
||||
return false;
|
||||
}
|
||||
@ -701,6 +717,7 @@ process()
|
||||
debug("Stream::process(%s): could not start, status=%d\n",
|
||||
name(), status);
|
||||
(void) recGblSetSevr(record, status, INVALID_ALARM);
|
||||
record->pact = false;
|
||||
return false;
|
||||
}
|
||||
debug("Stream::process(%s): protocol started\n", name());
|
||||
@ -754,7 +771,8 @@ scan(format_t *format, void* value, size_t maxStringSize)
|
||||
break;
|
||||
case DBF_STRING:
|
||||
sptr = (char*)value;
|
||||
currentValueLength = scanValue(*format->priv, sptr, maxStringSize);
|
||||
currentValueLength = scanValue(*format->priv, sptr,
|
||||
maxStringSize);
|
||||
break;
|
||||
default:
|
||||
error("INTERNAL ERROR (%s): Illegal format type\n", name());
|
||||
@ -772,24 +790,30 @@ scan(format_t *format, void* value, size_t maxStringSize)
|
||||
|
||||
// epicsTimerNotify virtual method ///////////////////////////////////////
|
||||
|
||||
#ifdef EPICS_3_14
|
||||
epicsTimerNotify::expireStatus Stream::
|
||||
expire(const epicsTime&)
|
||||
{
|
||||
timerCallback();
|
||||
return noRestart;
|
||||
}
|
||||
#else
|
||||
#ifdef EPICS_3_13
|
||||
void Stream::
|
||||
expire(CALLBACK *pcallback)
|
||||
{
|
||||
Stream* pstream = static_cast<Stream*>(pcallback->user);
|
||||
pstream->timerCallback();
|
||||
}
|
||||
#else
|
||||
epicsTimerNotify::expireStatus Stream::
|
||||
expire(const epicsTime&)
|
||||
{
|
||||
timerCallback();
|
||||
return noRestart;
|
||||
}
|
||||
#endif
|
||||
|
||||
// StreamCore virtual methods ////////////////////////////////////////////
|
||||
|
||||
void Stream::
|
||||
protocolStartHook()
|
||||
{
|
||||
flags &= ~Aborted;
|
||||
}
|
||||
|
||||
void Stream::
|
||||
protocolFinishHook(ProtocolResult result)
|
||||
{
|
||||
@ -824,6 +848,7 @@ protocolFinishHook(ProtocolResult result)
|
||||
status = CALC_ALARM;
|
||||
break;
|
||||
case Abort:
|
||||
flags |= Aborted;
|
||||
case Fault:
|
||||
status = UDF_ALARM;
|
||||
if (record->pact || record->scan == SCAN_IO_EVENT)
|
||||
@ -838,35 +863,53 @@ protocolFinishHook(ProtocolResult result)
|
||||
}
|
||||
if (flags & InitRun)
|
||||
{
|
||||
#ifdef EPICS_3_14
|
||||
initDone.signal();
|
||||
#else
|
||||
#ifdef EPICS_3_13
|
||||
semGive(initDone);
|
||||
#else
|
||||
initDone.signal();
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
if (record->pact || record->scan == SCAN_IO_EVENT)
|
||||
{
|
||||
debug("Stream::protocolFinishHook(stream=%s,result=%d) "
|
||||
"processing record\n", name(), result);
|
||||
// process record
|
||||
// This will call streamReadWrite.
|
||||
dbScanLock(record);
|
||||
((DEVSUPFUN)record->rset->process)(record);
|
||||
dbScanUnlock(record);
|
||||
|
||||
debug("Stream::protocolFinishHook(stream=%s,result=%d) done\n",
|
||||
name(), result);
|
||||
}
|
||||
if (result != Abort && record->scan == SCAN_IO_EVENT)
|
||||
{
|
||||
// re-enable early input
|
||||
flags |= AcceptInput;
|
||||
}
|
||||
|
||||
if (record->pact || record->scan == SCAN_IO_EVENT)
|
||||
{
|
||||
// process record in callback thread to break possible recursion
|
||||
callbackSetPriority(priority(), &processCallback);
|
||||
callbackRequest(&processCallback);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void streamRecordProcessCallback(CALLBACK *pcallback)
|
||||
{
|
||||
Stream* pstream = static_cast<Stream*>(pcallback->user);
|
||||
dbCommon* record = pstream->record;
|
||||
|
||||
// process record
|
||||
// This will call streamReadWrite.
|
||||
debug("streamRecordProcessCallback(%s) processing record\n",
|
||||
pstream->name());
|
||||
dbScanLock(record);
|
||||
((DEVSUPFUN)record->rset->process)(record);
|
||||
dbScanUnlock(record);
|
||||
debug("streamRecordProcessCallback(%s) processing record done\n",
|
||||
pstream->name());
|
||||
|
||||
if (record->scan == SCAN_IO_EVENT && !(pstream->flags & Aborted))
|
||||
{
|
||||
// restart protocol for next turn
|
||||
debug("Stream::process(%s) restart async protocol\n",
|
||||
name());
|
||||
if (!startProtocol(StartAsync))
|
||||
debug("streamRecordProcessCallback(%s) restart async protocol\n",
|
||||
pstream->name());
|
||||
if (!pstream->startProtocol(Stream::StartAsync))
|
||||
{
|
||||
error("%s: Can't restart \"I/O Intr\" protocol\n",
|
||||
name());
|
||||
pstream->name());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -876,13 +919,13 @@ startTimer(unsigned long timeout)
|
||||
{
|
||||
debug("Stream::startTimer(stream=%s, timeout=%lu) = %f seconds\n",
|
||||
name(), timeout, timeout * 0.001);
|
||||
#ifdef EPICS_3_14
|
||||
timer->start(*this, timeout * 0.001);
|
||||
#else
|
||||
#ifdef EPICS_3_13
|
||||
callbackSetPriority(priority(), &timeoutCallback);
|
||||
wdStart(timer, (timeout+1)*sysClkRateGet()/1000-1,
|
||||
reinterpret_cast<FUNCPTR>(callbackRequest),
|
||||
reinterpret_cast<int>(&timeoutCallback));
|
||||
#else
|
||||
timer->start(*this, timeout * 0.001);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -897,10 +940,15 @@ getFieldAddress(const char* fieldname, StreamBuffer& address)
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIELD in this record
|
||||
// FIELD in this record or VAL in other record
|
||||
char fullname[PVNAME_SZ + 1];
|
||||
sprintf(fullname, "%s.%s", name(), fieldname);
|
||||
if (dbNameToAddr(fullname, &dbaddr) != OK) return false;
|
||||
if (dbNameToAddr(fullname, &dbaddr) != OK)
|
||||
{
|
||||
// VAL in other record
|
||||
sprintf(fullname, "%s.VAL", fieldname);
|
||||
if (dbNameToAddr(fullname, &dbaddr) != OK) return false;
|
||||
}
|
||||
}
|
||||
address.append(&dbaddr, sizeof(dbaddr));
|
||||
return true;
|
||||
@ -930,6 +978,29 @@ formatValue(const StreamFormat& format, const void* fieldaddress)
|
||||
long nelem = pdbaddr->no_elements;
|
||||
size_t size = nelem * typeSize[format.type];
|
||||
char* buffer = fieldBuffer.clear().reserve(size);
|
||||
|
||||
if (strcmp(((dbFldDes*)pdbaddr->pfldDes)->name, "TIME") == 0)
|
||||
{
|
||||
double time;
|
||||
|
||||
if (format.type != double_format)
|
||||
{
|
||||
error ("%s: can only read double values from TIME field\n",
|
||||
name());
|
||||
return false;
|
||||
}
|
||||
if (pdbaddr->precord == record)
|
||||
{
|
||||
/* if getting time from own record, update timestamp first */
|
||||
recGblGetTimeStamp(record);
|
||||
}
|
||||
time = pdbaddr->precord->time.secPastEpoch +
|
||||
631152000u + pdbaddr->precord->time.nsec * 1e-9;
|
||||
debug("Stream::formatValue(%s): read %f from TIME field\n",
|
||||
name(), time);
|
||||
return printValue(format, time);
|
||||
}
|
||||
|
||||
if (dbGet(pdbaddr, dbfMapping[format.type], buffer,
|
||||
NULL, &nelem, NULL) != 0)
|
||||
{
|
||||
@ -964,7 +1035,8 @@ formatValue(const StreamFormat& format, const void* fieldaddress)
|
||||
return false;
|
||||
break;
|
||||
case pseudo_format:
|
||||
error("%s: %%(FIELD) syntax not allowed with pseudo formats\n",
|
||||
error("%s: %%(FIELD) syntax not allowed "
|
||||
"with pseudo formats\n",
|
||||
name());
|
||||
default:
|
||||
error("INTERNAL ERROR %s: Illegal format.type=%d\n",
|
||||
@ -996,14 +1068,13 @@ bool Stream::
|
||||
matchValue(const StreamFormat& format, const void* fieldaddress)
|
||||
{
|
||||
// this function must increase consumedInput
|
||||
// [I use goto and feel very ashamed for it.]
|
||||
long consumed;
|
||||
long lval;
|
||||
double dval;
|
||||
char* buffer;
|
||||
int status;
|
||||
const char* putfunc;
|
||||
|
||||
|
||||
if (fieldaddress)
|
||||
{
|
||||
// Format like "%([record.]field)..." has requested to put value
|
||||
@ -1015,34 +1086,49 @@ matchValue(const StreamFormat& format, const void* fieldaddress)
|
||||
buffer = fieldBuffer.clear().reserve(size);
|
||||
for (nord = 0; nord < nelem; nord++)
|
||||
{
|
||||
debug("Stream::matchValue(%s): buffer before: %s\n",
|
||||
name(), fieldBuffer.expand()());
|
||||
switch (format.type)
|
||||
{
|
||||
case long_format:
|
||||
{
|
||||
consumed = scanValue(format, lval);
|
||||
if (consumed < 0) goto noMoreElements;
|
||||
((epicsInt32*)buffer)[nord] = lval;
|
||||
if (consumed >= 0) ((epicsInt32*)buffer)[nord] = lval;
|
||||
debug("Stream::matchValue(%s): %s[%li] = %li\n",
|
||||
name(), pdbaddr->precord->name, nord, lval);
|
||||
break;
|
||||
}
|
||||
case enum_format:
|
||||
{
|
||||
consumed = scanValue(format, lval);
|
||||
if (consumed < 0) goto noMoreElements;
|
||||
((epicsUInt16*)buffer)[nord] = (epicsUInt16)lval;
|
||||
if (consumed >= 0)
|
||||
((epicsUInt16*)buffer)[nord] = (epicsUInt16)lval;
|
||||
debug("Stream::matchValue(%s): %s[%li] = %li\n",
|
||||
name(), pdbaddr->precord->name, nord, lval);
|
||||
break;
|
||||
}
|
||||
case double_format:
|
||||
{
|
||||
consumed = scanValue(format, dval);
|
||||
if (consumed < 0) goto noMoreElements;
|
||||
((epicsFloat64*)buffer)[nord] = dval;
|
||||
// Direct assignment to buffer fails with
|
||||
// gcc 3.4.3 for xscale_be
|
||||
// Optimization bug?
|
||||
epicsFloat64 f64=dval;
|
||||
if (consumed >= 0)
|
||||
memcpy(((epicsFloat64*)buffer)+nord,
|
||||
&f64, sizeof(f64));
|
||||
debug("Stream::matchValue(%s): %s[%li] = %#g %#g\n",
|
||||
name(), pdbaddr->precord->name, nord, dval,
|
||||
((epicsFloat64*)buffer)[nord]);
|
||||
break;
|
||||
}
|
||||
case string_format:
|
||||
{
|
||||
consumed = scanValue(format,
|
||||
buffer+MAX_STRING_SIZE*nord, MAX_STRING_SIZE);
|
||||
if (consumed < 0) goto noMoreElements;
|
||||
debug("Stream::matchValue(%s): %s[%li] = \"%.*s\"\n",
|
||||
name(), pdbaddr->precord->name, nord,
|
||||
MAX_STRING_SIZE, buffer+MAX_STRING_SIZE*nord);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -1050,15 +1136,18 @@ matchValue(const StreamFormat& format, const void* fieldaddress)
|
||||
"Illegal format type\n", name());
|
||||
return false;
|
||||
}
|
||||
debug("Stream::matchValue(%s): buffer after: %s\n",
|
||||
name(), fieldBuffer.expand()());
|
||||
if (consumed < 0) break;
|
||||
consumedInput += consumed;
|
||||
}
|
||||
noMoreElements:
|
||||
if (!nord)
|
||||
{
|
||||
// scan error: set other record to alarm status
|
||||
if (pdbaddr->precord != record)
|
||||
{
|
||||
recGblSetSevr(pdbaddr->precord, CALC_ALARM, INVALID_ALARM);
|
||||
(void) recGblSetSevr(pdbaddr->precord,
|
||||
CALC_ALARM, INVALID_ALARM);
|
||||
if (!INIT_RUN)
|
||||
{
|
||||
// process other record to send alarm monitor
|
||||
@ -1067,16 +1156,44 @@ noMoreElements:
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (strcmp(((dbFldDes*)pdbaddr->pfldDes)->name, "TIME") == 0)
|
||||
{
|
||||
#ifdef epicsTimeEventDeviceTime
|
||||
if (format.type != double_format)
|
||||
{
|
||||
error ("%s: can only write double values to TIME field\n",
|
||||
name());
|
||||
return false;
|
||||
}
|
||||
dval = dval-631152000u;
|
||||
pdbaddr->precord->time.secPastEpoch = (long)dval;
|
||||
// rouding: we don't have 9 digits precision
|
||||
// in a double of today's number of seconds
|
||||
pdbaddr->precord->time.nsec = (long)((dval-(long)dval)*1e6)*1000;
|
||||
debug("Stream::matchValue(%s): writing %i.%i to TIME field\n",
|
||||
name(),
|
||||
pdbaddr->precord->time.secPastEpoch,
|
||||
pdbaddr->precord->time.nsec);
|
||||
pdbaddr->precord->tse = epicsTimeEventDeviceTime;
|
||||
return true;
|
||||
#else
|
||||
error ("%s: writing TIME field is not supported "
|
||||
"in this EPICS version\n", name());
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (pdbaddr->precord == record || INIT_RUN)
|
||||
{
|
||||
// write into own record, thus don't process it
|
||||
// in @init we must not process other record
|
||||
debug("Stream::matchValue(%s): dbPut(%s.%s,...)\n",
|
||||
debug("Stream::matchValue(%s): dbPut(%s.%s,%s)\n",
|
||||
name(),
|
||||
pdbaddr->precord->name,
|
||||
((dbFldDes*)pdbaddr->pfldDes)->name);
|
||||
((dbFldDes*)pdbaddr->pfldDes)->name,
|
||||
fieldBuffer.expand()());
|
||||
putfunc = "dbPut";
|
||||
status = dbPut(pdbaddr, dbfMapping[format.type], fieldBuffer(), nord);
|
||||
status = dbPut(pdbaddr, dbfMapping[format.type], buffer, nord);
|
||||
if (INIT_RUN && pdbaddr->precord != record)
|
||||
{
|
||||
// clean error status of other record in @init
|
||||
@ -1088,12 +1205,14 @@ noMoreElements:
|
||||
else
|
||||
{
|
||||
// write into other record, thus process it
|
||||
debug("Stream::matchValue(%s): dbPutField(%s.%s,...)\n",
|
||||
debug("Stream::matchValue(%s): dbPutField(%s.%s,%s)\n",
|
||||
name(),
|
||||
pdbaddr->precord->name,
|
||||
((dbFldDes*)pdbaddr->pfldDes)->name);
|
||||
((dbFldDes*)pdbaddr->pfldDes)->name,
|
||||
fieldBuffer.expand()());
|
||||
putfunc = "dbPutField";
|
||||
status = dbPutField(pdbaddr, dbfMapping[format.type], fieldBuffer(), nord);
|
||||
status = dbPutField(pdbaddr, dbfMapping[format.type],
|
||||
buffer, nord);
|
||||
}
|
||||
if (status != 0)
|
||||
{
|
||||
@ -1156,13 +1275,15 @@ noMoreElements:
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef EPICS_3_14
|
||||
#ifdef EPICS_3_13
|
||||
// Pass command to vxWorks shell
|
||||
extern "C" int execute(const char *cmd);
|
||||
|
||||
void streamExecuteCommand(CALLBACK *pcallback)
|
||||
{
|
||||
Stream* pstream = static_cast<Stream*>(pcallback->user);
|
||||
|
||||
if (iocshCmd(pstream->outputLine()) != OK)
|
||||
|
||||
if (execute(pstream->outputLine()) != OK)
|
||||
{
|
||||
pstream->execCallback(StreamIoFault);
|
||||
}
|
||||
@ -1172,13 +1293,12 @@ void streamExecuteCommand(CALLBACK *pcallback)
|
||||
}
|
||||
}
|
||||
#else
|
||||
extern "C" int execute (const char *cmd);
|
||||
|
||||
// Pass command to iocsh
|
||||
void streamExecuteCommand(CALLBACK *pcallback)
|
||||
{
|
||||
Stream* pstream = static_cast<Stream*>(pcallback->user);
|
||||
|
||||
if (execute(pstream->outputLine()) != OK)
|
||||
|
||||
if (iocshCmd(pstream->outputLine()) != OK)
|
||||
{
|
||||
pstream->execCallback(StreamIoFault);
|
||||
}
|
||||
@ -1200,19 +1320,19 @@ execute()
|
||||
void Stream::
|
||||
lockMutex()
|
||||
{
|
||||
#ifdef EPICS_3_14
|
||||
mutex.lock();
|
||||
#else
|
||||
#ifdef EPICS_3_13
|
||||
semTake(mutex, WAIT_FOREVER);
|
||||
#else
|
||||
mutex.lock();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Stream::
|
||||
releaseMutex()
|
||||
{
|
||||
#ifdef EPICS_3_14
|
||||
mutex.unlock();
|
||||
#else
|
||||
#ifdef EPICS_3_13
|
||||
semGive(mutex);
|
||||
#else
|
||||
mutex.unlock();
|
||||
#endif
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "StreamError.h"
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int streamDebug = 0;
|
||||
extern "C" {
|
||||
@ -58,11 +59,19 @@ void StreamError(const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
StreamVError(fmt, args);
|
||||
StreamVError(0, NULL, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void StreamVError(const char* fmt, va_list args)
|
||||
void StreamError(int line, const char* file, const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
StreamVError(line, file, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void StreamVError(int line, const char* file, const char* fmt, va_list args)
|
||||
{
|
||||
char timestamp[40];
|
||||
StreamPrintTimestampFunction(timestamp, 40);
|
||||
@ -79,6 +88,10 @@ void StreamVError(const char* fmt, va_list args)
|
||||
#endif
|
||||
fprintf(stderr, "\033[31;1m");
|
||||
fprintf(stderr, "%s ", timestamp);
|
||||
if (file)
|
||||
{
|
||||
fprintf(stderr, "%s line %d: ", file, line);
|
||||
}
|
||||
vfprintf(stderr, fmt, args);
|
||||
fprintf(stderr, "\033[0m");
|
||||
}
|
||||
|
@ -20,8 +20,8 @@
|
||||
#ifndef StreamError_h
|
||||
#define StreamError_h
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifndef __GNUC__
|
||||
#define __attribute__(x)
|
||||
@ -30,11 +30,19 @@
|
||||
extern int streamDebug;
|
||||
extern void (*StreamPrintTimestampFunction)(char* buffer, int size);
|
||||
|
||||
void StreamError(int line, const char* file, const char* fmt, ...)
|
||||
__attribute__ ((format(printf,3,4)));
|
||||
|
||||
void StreamVError(int line, const char* file, const char* fmt, va_list args)
|
||||
__attribute__ ((format(printf,3,0)));
|
||||
|
||||
void StreamError(const char* fmt, ...)
|
||||
__attribute__ ((format(printf,1,2)));
|
||||
|
||||
void StreamVError(const char* fmt, va_list args)
|
||||
__attribute__ ((format(printf,1,0)));
|
||||
inline void StreamVError(const char* fmt, va_list args)
|
||||
{
|
||||
StreamVError(0, NULL, fmt, args);
|
||||
}
|
||||
|
||||
class StreamDebugClass
|
||||
{
|
||||
|
@ -23,12 +23,14 @@
|
||||
#define StreamFormat_h
|
||||
|
||||
typedef enum {
|
||||
left_flag = 0x01,
|
||||
sign_flag = 0x02,
|
||||
space_flag = 0x04,
|
||||
alt_flag = 0x08,
|
||||
zero_flag = 0x10,
|
||||
skip_flag = 0x20
|
||||
left_flag = 0x01,
|
||||
sign_flag = 0x02,
|
||||
space_flag = 0x04,
|
||||
alt_flag = 0x08,
|
||||
zero_flag = 0x10,
|
||||
skip_flag = 0x20,
|
||||
default_flag = 0x40,
|
||||
compare_flag = 0x80
|
||||
} StreamFormatFlag;
|
||||
|
||||
typedef enum {
|
||||
|
@ -19,8 +19,9 @@
|
||||
* *
|
||||
***************************************************************/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include "StreamFormatConverter.h"
|
||||
#include "StreamFormat.h"
|
||||
#include "StreamError.h"
|
||||
|
||||
StreamFormatConverter* StreamFormatConverter::
|
||||
@ -31,6 +32,125 @@ StreamFormatConverter::
|
||||
{
|
||||
}
|
||||
|
||||
int StreamFormatConverter::
|
||||
parseFormat(const char*& source, FormatType formatType, StreamFormat& streamFormat, StreamBuffer& infoString)
|
||||
{
|
||||
/*
|
||||
source := [flags] [width] ['.' prec] conv [extra]
|
||||
flags := '-' | '+' | ' ' | '#' | '0' | '*' | '?' | '='
|
||||
width := integer
|
||||
prec := integer
|
||||
conv := character
|
||||
extra := string
|
||||
*/
|
||||
|
||||
// look for flags
|
||||
streamFormat.flags = 0;
|
||||
bool loop = true;
|
||||
while (loop)
|
||||
{
|
||||
switch (*++source)
|
||||
{
|
||||
case '-':
|
||||
streamFormat.flags |= left_flag;
|
||||
break;
|
||||
case '+':
|
||||
streamFormat.flags |= sign_flag;
|
||||
break;
|
||||
case ' ':
|
||||
streamFormat.flags |= space_flag;
|
||||
break;
|
||||
case '#':
|
||||
streamFormat.flags |= alt_flag;
|
||||
break;
|
||||
case '0':
|
||||
streamFormat.flags |= zero_flag;
|
||||
break;
|
||||
case '*':
|
||||
if (formatType != ScanFormat)
|
||||
{
|
||||
error("Use of skip modifier '*' "
|
||||
"only allowed in input formats\n");
|
||||
return false;
|
||||
}
|
||||
streamFormat.flags |= skip_flag;
|
||||
break;
|
||||
case '?':
|
||||
if (formatType != ScanFormat)
|
||||
{
|
||||
error("Use of default modifier '?' "
|
||||
"only allowed in input formats\n");
|
||||
return false;
|
||||
}
|
||||
streamFormat.flags |= default_flag;
|
||||
break;
|
||||
case '=':
|
||||
if (formatType != ScanFormat)
|
||||
{
|
||||
error("Use of compare modifier '=' "
|
||||
"only allowed in input formats\n");
|
||||
return false;
|
||||
}
|
||||
streamFormat.flags |= compare_flag;
|
||||
formatType = PrintFormat;
|
||||
break;
|
||||
default:
|
||||
loop = false;
|
||||
}
|
||||
}
|
||||
// look for width
|
||||
unsigned long val;
|
||||
char* p;
|
||||
val = strtoul (source, &p, 10);
|
||||
source = p;
|
||||
if (val > 0xFFFF)
|
||||
{
|
||||
error("Field width %ld out of range\n", val);
|
||||
return false;
|
||||
}
|
||||
streamFormat.width = (unsigned short)val;
|
||||
// look for prec
|
||||
streamFormat.prec = -1;
|
||||
if (*source == '.')
|
||||
{
|
||||
source++;
|
||||
val = strtoul(source, &p, 10);
|
||||
if (p == source)
|
||||
{
|
||||
debug("source = %s\n", source);
|
||||
error("Numeric precision field expected after '.'\n");
|
||||
return false;
|
||||
}
|
||||
source = p;
|
||||
if (val > 0x7FFF)
|
||||
{
|
||||
error("Precision %ld out of range\n", val);
|
||||
return false;
|
||||
}
|
||||
streamFormat.prec = (short)val;
|
||||
}
|
||||
// look for converter
|
||||
streamFormat.conv = *source++;
|
||||
if (!streamFormat.conv || strchr("'\" (.0+-*?=", streamFormat.conv))
|
||||
{
|
||||
error("Missing converter character\n");
|
||||
return false;
|
||||
}
|
||||
debug("StreamFormatConverter::parseFormat: converter='%c'\n",
|
||||
streamFormat.conv);
|
||||
StreamFormatConverter* converter;
|
||||
converter = StreamFormatConverter::find(streamFormat.conv);
|
||||
if (!converter)
|
||||
{
|
||||
error("No converter registered for format '%%%c'\n",
|
||||
streamFormat.conv);
|
||||
return false;
|
||||
}
|
||||
// parse format and get info string
|
||||
return converter->parse(streamFormat, infoString,
|
||||
source, formatType == ScanFormat);
|
||||
}
|
||||
|
||||
void StreamFormatConverter::
|
||||
provides(const char* name, const char* provided)
|
||||
{
|
||||
@ -112,11 +232,57 @@ static void copyFormatString(StreamBuffer& info, const char* source)
|
||||
const char* p = source - 1;
|
||||
while (*p != '%' && *p != ')') p--;
|
||||
info.append('%');
|
||||
while (++p != source-1) info.append(*p);
|
||||
while (++p != source-1) if (*p != '?' && *p != '=') info.append(*p);
|
||||
}
|
||||
|
||||
// A word on sscanf
|
||||
// GNU's sscanf implementation sucks. It calls strlen on the buffer.
|
||||
// That leads to a time consumption proportional to the buffer size.
|
||||
// When reading huge arrays element-wise by repeatedly calling sscanf,
|
||||
// the performance drops to O(n^2).
|
||||
// The vxWorks implementation sucks, too. When all parsed values are skipped
|
||||
// with %*, it returns -1 instead of 0 even though it was successful.
|
||||
|
||||
// Standard Long Converter for 'diouxX'
|
||||
|
||||
static int prepareval(const StreamFormat& fmt, const char*& input, bool& neg)
|
||||
{
|
||||
int length = 0;
|
||||
neg = false;
|
||||
while (isspace(*input)) { input++; length++; }
|
||||
if (fmt.width)
|
||||
{
|
||||
// take local copy because strto* don't have width parameter
|
||||
int width = fmt.width;
|
||||
if (fmt.flags & space_flag)
|
||||
{
|
||||
// normally whitespace does not count to width
|
||||
// but do so if space flag is present
|
||||
width -= length;
|
||||
}
|
||||
strncpy((char*)fmt.info, input, width);
|
||||
((char*)fmt.info)[width] = 0;
|
||||
input = fmt.info;
|
||||
}
|
||||
if (*input == '+')
|
||||
{
|
||||
goto skipsign;
|
||||
}
|
||||
if (*input == '-')
|
||||
{
|
||||
neg = true;
|
||||
skipsign:
|
||||
input++;
|
||||
length++;
|
||||
}
|
||||
if (isspace(*input))
|
||||
{
|
||||
// allow space after sign only if # flag is set
|
||||
if (!(fmt.flags & alt_flag)) return -1;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
class StdLongConverter : public StreamFormatConverter
|
||||
{
|
||||
int parse(const StreamFormat& fmt, StreamBuffer& output, const char*& value, bool scanFormat);
|
||||
@ -128,44 +294,68 @@ int StdLongConverter::
|
||||
parse(const StreamFormat& fmt, StreamBuffer& info,
|
||||
const char*& source, bool scanFormat)
|
||||
{
|
||||
if (scanFormat && (fmt.flags & alt_flag))
|
||||
{
|
||||
error("Use of modifier '#' not allowed with %%%c input conversion\n",
|
||||
fmt.conv);
|
||||
return false;
|
||||
}
|
||||
if (scanFormat && fmt.prec >= 0)
|
||||
{
|
||||
error("Use of precision field '.%d' not allowed with %%%c input conversion\n",
|
||||
fmt.prec, fmt.conv);
|
||||
return false;
|
||||
}
|
||||
copyFormatString(info, source);
|
||||
info.append('l');
|
||||
info.append(fmt.conv);
|
||||
if (scanFormat) info.append("%n");
|
||||
if (scanFormat)
|
||||
{
|
||||
if (fmt.width) info.reserve(fmt.width+1);
|
||||
}
|
||||
else
|
||||
{
|
||||
copyFormatString(info, source);
|
||||
info.append('l');
|
||||
info.append(fmt.conv);
|
||||
}
|
||||
return long_format;
|
||||
}
|
||||
|
||||
bool StdLongConverter::
|
||||
printLong(const StreamFormat& fmt, StreamBuffer& output, long value)
|
||||
{
|
||||
output.printf(fmt.info, value);
|
||||
output.print(fmt.info, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
int StdLongConverter::
|
||||
scanLong(const StreamFormat& fmt, const char* input, long& value)
|
||||
{
|
||||
int length = -1;
|
||||
if (fmt.flags & skip_flag)
|
||||
char* end;
|
||||
int length;
|
||||
bool neg;
|
||||
int base;
|
||||
|
||||
length = prepareval(fmt, input, neg);
|
||||
if (length < 0) return -1;
|
||||
switch (fmt.conv)
|
||||
{
|
||||
if (sscanf(input, fmt.info, &length) < 0) return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sscanf(input, fmt.info, &value, &length) < 1) return -1;
|
||||
case 'd':
|
||||
base = 10;
|
||||
break;
|
||||
case 'o':
|
||||
base = 8;
|
||||
goto signcheck;
|
||||
case 'x':
|
||||
case 'X':
|
||||
base = 16;
|
||||
signcheck:
|
||||
// allow negative hex and oct numbers with - flag
|
||||
if (neg && !(fmt.flags & left_flag)) return -1;
|
||||
break;
|
||||
case 'u':
|
||||
if (neg) return -1;
|
||||
base = 10;
|
||||
break;
|
||||
default:
|
||||
base = 0;
|
||||
}
|
||||
value = strtoul(input, &end, base);
|
||||
if (neg) value = -value;
|
||||
if (end == input) return -1;
|
||||
length += end-input;
|
||||
return length;
|
||||
}
|
||||
|
||||
@ -184,44 +374,44 @@ int StdDoubleConverter::
|
||||
parse(const StreamFormat& fmt, StreamBuffer& info,
|
||||
const char*& source, bool scanFormat)
|
||||
{
|
||||
if (scanFormat && (fmt.flags & alt_flag))
|
||||
{
|
||||
error("Use of modifier '#' not allowed with %%%c input conversion\n",
|
||||
fmt.conv);
|
||||
return false;
|
||||
}
|
||||
if (scanFormat && fmt.prec >= 0)
|
||||
{
|
||||
error("Use of precision field '.%d' not allowed with %%%c input conversion\n",
|
||||
fmt.prec, fmt.conv);
|
||||
return false;
|
||||
}
|
||||
copyFormatString(info, source);
|
||||
if (scanFormat) info.append('l');
|
||||
info.append(fmt.conv);
|
||||
if (scanFormat) info.append("%n");
|
||||
if (scanFormat)
|
||||
{
|
||||
if (fmt.width) info.reserve(fmt.width+1);
|
||||
}
|
||||
else
|
||||
{
|
||||
copyFormatString(info, source);
|
||||
info.append(fmt.conv);
|
||||
}
|
||||
return double_format;
|
||||
}
|
||||
|
||||
bool StdDoubleConverter::
|
||||
printDouble(const StreamFormat& fmt, StreamBuffer& output, double value)
|
||||
{
|
||||
output.printf(fmt.info, value);
|
||||
output.print(fmt.info, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
int StdDoubleConverter::
|
||||
scanDouble(const StreamFormat& fmt, const char* input, double& value)
|
||||
{
|
||||
int length = -1;
|
||||
if (fmt.flags & skip_flag)
|
||||
{
|
||||
if (sscanf(input, fmt.info, &length) < 0) return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sscanf(input, fmt.info, &value, &length) < 1) return -1;
|
||||
}
|
||||
char* end;
|
||||
int length;
|
||||
bool neg;
|
||||
|
||||
length = prepareval(fmt, input, neg);
|
||||
if (length < 0) return -1;
|
||||
value = strtod(input, &end);
|
||||
if (neg) value = -value;
|
||||
if (end == input) return -1;
|
||||
length += end-input;
|
||||
return length;
|
||||
}
|
||||
|
||||
@ -240,11 +430,10 @@ int StdStringConverter::
|
||||
parse(const StreamFormat& fmt, StreamBuffer& info,
|
||||
const char*& source, bool scanFormat)
|
||||
{
|
||||
if (fmt.flags & (sign_flag|space_flag|zero_flag|alt_flag))
|
||||
if (fmt.flags & (sign_flag|zero_flag))
|
||||
{
|
||||
error("Use of modifiers '+', ' ', '0', '#' "
|
||||
"not allowed with %%%c conversion\n",
|
||||
fmt.conv);
|
||||
error("Use of modifiers '+', '0'"
|
||||
"not allowed with %%s conversion\n");
|
||||
return false;
|
||||
}
|
||||
if (scanFormat && fmt.prec >= 0)
|
||||
@ -262,7 +451,7 @@ parse(const StreamFormat& fmt, StreamBuffer& info,
|
||||
bool StdStringConverter::
|
||||
printString(const StreamFormat& fmt, StreamBuffer& output, const char* value)
|
||||
{
|
||||
output.printf(fmt.info, value);
|
||||
output.print(fmt.info, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -270,38 +459,52 @@ int StdStringConverter::
|
||||
scanString(const StreamFormat& fmt, const char* input,
|
||||
char* value, size_t maxlen)
|
||||
{
|
||||
int length = -1;
|
||||
if (*input == '\0')
|
||||
int length = 0;
|
||||
|
||||
int width = fmt.width;
|
||||
|
||||
if ((fmt.flags & skip_flag) || value == NULL) maxlen = 0;
|
||||
|
||||
// if user does not specify width assume "ininity" (-1)
|
||||
if (width == 0)
|
||||
{
|
||||
// match empty string
|
||||
value[0] = '\0';
|
||||
return 0;
|
||||
if (fmt.conv == 'c') width = 1;
|
||||
else width = -1;
|
||||
}
|
||||
if (fmt.flags & skip_flag)
|
||||
|
||||
while (isspace(*input) && width)
|
||||
{
|
||||
if (sscanf(input, fmt.info, &length) < 0) return -1;
|
||||
// normally leading whitespace does not count to width
|
||||
// but do so if space flag is present
|
||||
if (fmt.flags & space_flag)
|
||||
{
|
||||
if (maxlen > 1)
|
||||
{
|
||||
*value++ = *input;
|
||||
maxlen--;
|
||||
}
|
||||
width--;
|
||||
}
|
||||
length++;
|
||||
input++;
|
||||
}
|
||||
else
|
||||
while (*input && width)
|
||||
{
|
||||
char tmpformat[10];
|
||||
const char* f;
|
||||
if (maxlen <= fmt.width || fmt.width == 0)
|
||||
// normally whitespace ends string
|
||||
// but don't end if # flag is present
|
||||
if (!(fmt.flags & alt_flag) && isspace(*input)) break;
|
||||
if (maxlen > 1)
|
||||
{
|
||||
// assure not to read too much
|
||||
sprintf(tmpformat, "%%%d%c%%n", maxlen-1, fmt.conv);
|
||||
f = tmpformat;
|
||||
*value++ = *input;
|
||||
maxlen--;
|
||||
}
|
||||
else
|
||||
{
|
||||
f = fmt.info;
|
||||
}
|
||||
if (sscanf(input, f, value, &length) < 1) return -1;
|
||||
if (length < 0) return -1;
|
||||
value[length] = '\0';
|
||||
#ifndef NO_TEMPORARY
|
||||
debug("StdStringConverter::scanString: length=%d, value=%s\n",
|
||||
length, StreamBuffer(value,length).expand()());
|
||||
#endif
|
||||
length++;
|
||||
width--;
|
||||
input++;
|
||||
}
|
||||
if (maxlen > 0)
|
||||
{
|
||||
*value = '\0';
|
||||
}
|
||||
return length;
|
||||
}
|
||||
@ -310,11 +513,11 @@ RegisterConverter (StdStringConverter, "s");
|
||||
|
||||
// Standard Characters Converter for 'c'
|
||||
|
||||
class StdCharsConverter : public StdStringConverter
|
||||
class StdCharsConverter : public StreamFormatConverter
|
||||
{
|
||||
virtual int parse(const StreamFormat&, StreamBuffer&, const char*&, bool);
|
||||
virtual bool printLong(const StreamFormat&, StreamBuffer&, long);
|
||||
// scanString is inherited from %s format
|
||||
virtual int scanString(const StreamFormat&, const char*, char*, size_t);
|
||||
};
|
||||
|
||||
int StdCharsConverter::
|
||||
@ -324,8 +527,7 @@ parse(const StreamFormat& fmt, StreamBuffer& info,
|
||||
if (fmt.flags & (sign_flag|space_flag|zero_flag|alt_flag))
|
||||
{
|
||||
error("Use of modifiers '+', ' ', '0', '#' "
|
||||
"not allowed with %%%c conversion\n",
|
||||
fmt.conv);
|
||||
"not allowed with %%c conversion\n");
|
||||
return false;
|
||||
}
|
||||
if (scanFormat && fmt.prec >= 0)
|
||||
@ -347,10 +549,41 @@ parse(const StreamFormat& fmt, StreamBuffer& info,
|
||||
bool StdCharsConverter::
|
||||
printLong(const StreamFormat& fmt, StreamBuffer& output, long value)
|
||||
{
|
||||
output.printf(fmt.info, value);
|
||||
output.print(fmt.info, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
int StdCharsConverter::
|
||||
scanString(const StreamFormat& fmt, const char* input,
|
||||
char* value, size_t maxlen)
|
||||
{
|
||||
int length = 0;
|
||||
|
||||
int width = fmt.width;
|
||||
|
||||
if ((fmt.flags & skip_flag) || value == NULL) maxlen = 0;
|
||||
|
||||
// if user does not specify width assume 1
|
||||
if (width == 0) width = 1;
|
||||
|
||||
while (*input && width)
|
||||
{
|
||||
if (maxlen > 1)
|
||||
{
|
||||
*value++ = *input;
|
||||
maxlen--;
|
||||
}
|
||||
length++;
|
||||
width--;
|
||||
input++;
|
||||
}
|
||||
if (maxlen > 0)
|
||||
{
|
||||
*value = '\0';
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
RegisterConverter (StdCharsConverter, "c");
|
||||
|
||||
// Standard Charset Converter for '['
|
||||
@ -362,6 +595,17 @@ class StdCharsetConverter : public StreamFormatConverter
|
||||
// no print method, %[ is readonly
|
||||
};
|
||||
|
||||
inline void markbit(StreamBuffer& info, bool notflag, char c)
|
||||
{
|
||||
char &infobyte = info[c>>3];
|
||||
char mask = 1<<(c&7);
|
||||
|
||||
if (notflag)
|
||||
infobyte |= mask;
|
||||
else
|
||||
infobyte &= ~mask;
|
||||
}
|
||||
|
||||
int StdCharsetConverter::
|
||||
parse(const StreamFormat& fmt, StreamBuffer& info,
|
||||
const char*& source, bool scanFormat)
|
||||
@ -374,8 +618,7 @@ parse(const StreamFormat& fmt, StreamBuffer& info,
|
||||
if (fmt.flags & (left_flag|sign_flag|space_flag|zero_flag|alt_flag))
|
||||
{
|
||||
error("Use of modifiers '-', '+', ' ', '0', '#'"
|
||||
"not allowed with %%%c conversion\n",
|
||||
fmt.conv);
|
||||
"not allowed with %%[ conversion\n");
|
||||
return false;
|
||||
}
|
||||
if (scanFormat && fmt.prec >= 0)
|
||||
@ -384,18 +627,46 @@ parse(const StreamFormat& fmt, StreamBuffer& info,
|
||||
fmt.prec, fmt.conv);
|
||||
return false;
|
||||
}
|
||||
info.printf("%%%s%d[", fmt.flags & skip_flag ? "*" : "", fmt.width);
|
||||
while (*source && *source != ']')
|
||||
|
||||
bool notflag = false;
|
||||
char c = 0;
|
||||
|
||||
info.clear().reserve(32);
|
||||
if (*source == '^')
|
||||
{
|
||||
if (*source == esc) source++;
|
||||
info.append(*source++);
|
||||
notflag = true;
|
||||
source++;
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(info(1), 255, 32);
|
||||
}
|
||||
if (*source == ']')
|
||||
{
|
||||
markbit(info, notflag, *source++);
|
||||
}
|
||||
for (; *source && *source != ']'; source++)
|
||||
{
|
||||
if (*source == esc)
|
||||
{
|
||||
markbit(info, notflag, *++source);
|
||||
continue;
|
||||
}
|
||||
if (*source == '-' && c && source[1] && source[1] != ']')
|
||||
{
|
||||
source++;
|
||||
while (c < *source) markbit(info, notflag, c++);
|
||||
while (c > *source) markbit(info, notflag, c--);
|
||||
}
|
||||
c = *source;
|
||||
markbit(info, notflag, c);
|
||||
}
|
||||
if (!*source) {
|
||||
error("Missing ']' after %%[ format conversion\n");
|
||||
return false;
|
||||
}
|
||||
source++; // consume ']'
|
||||
info.append("]%n");
|
||||
|
||||
return string_format;
|
||||
}
|
||||
|
||||
@ -403,31 +674,29 @@ int StdCharsetConverter::
|
||||
scanString(const StreamFormat& fmt, const char* input,
|
||||
char* value, size_t maxlen)
|
||||
{
|
||||
int length = -1;
|
||||
if (fmt.flags & skip_flag)
|
||||
int length = 0;
|
||||
int width = fmt.width;
|
||||
|
||||
if ((fmt.flags & skip_flag) || value == NULL) maxlen = 0;
|
||||
|
||||
// if user does not specify width assume "ininity" (-1)
|
||||
if (width == 0) width = -1;
|
||||
|
||||
while (*input && width)
|
||||
{
|
||||
if (sscanf (input, fmt.info, &length) < 0) return -1;
|
||||
if (fmt.info[*input>>3] & 1<<(*input&7)) break;
|
||||
if (maxlen > 1)
|
||||
{
|
||||
*value++ = *input;
|
||||
maxlen--;
|
||||
}
|
||||
length++;
|
||||
width--;
|
||||
input++;
|
||||
}
|
||||
else
|
||||
if (maxlen > 0)
|
||||
{
|
||||
char tmpformat[256];
|
||||
const char* f;
|
||||
if (maxlen <= fmt.width || fmt.width == 0)
|
||||
{
|
||||
const char *p = strchr (fmt.info, '[');
|
||||
// assure not to read too much
|
||||
sprintf(tmpformat, "%%%d%s", maxlen-1, p);
|
||||
f = tmpformat;
|
||||
}
|
||||
else
|
||||
{
|
||||
f = fmt.info;
|
||||
}
|
||||
if (sscanf(input, f, value, &length) < 1) return -1;
|
||||
if (length < 0) return -1;
|
||||
value[length] = '\0';
|
||||
debug("StdCharsetConverter::scanString: length=%d, value=%s\n",
|
||||
length, value);
|
||||
*value = '\0';
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
#include "StreamFormat.h"
|
||||
#include "StreamBuffer.h"
|
||||
#include "StreamProtocol.h"
|
||||
|
||||
#define esc (0x1b)
|
||||
|
||||
@ -41,6 +42,7 @@ class StreamFormatConverter
|
||||
const char* _name;
|
||||
public:
|
||||
virtual ~StreamFormatConverter();
|
||||
static int parseFormat(const char*& source, FormatType, StreamFormat&, StreamBuffer& infoString);
|
||||
const char* name() { return _name; }
|
||||
void provides(const char* name, const char* provided);
|
||||
static StreamFormatConverter* find(unsigned char c);
|
||||
|
@ -81,18 +81,6 @@ StreamProtocolParser::
|
||||
delete next;
|
||||
}
|
||||
|
||||
void StreamProtocolParser::
|
||||
errorMsg(const char* fmt, ...)
|
||||
{
|
||||
char fmt2[200];
|
||||
sprintf (fmt2, "'%s' line %d: %s",
|
||||
filename(), line, fmt);
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
StreamVError(fmt2, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void StreamProtocolParser::
|
||||
report()
|
||||
{
|
||||
@ -118,7 +106,7 @@ getProtocol(const char* filename, const StreamBuffer& protocolAndParams)
|
||||
// Have we already seen this file?
|
||||
for (parser = parsers; parser; parser = parser->next)
|
||||
{
|
||||
if (parser->filename.equals(filename))
|
||||
if (parser->filename.startswith(filename))
|
||||
{
|
||||
if (!parser->valid)
|
||||
{
|
||||
@ -234,12 +222,12 @@ getProtocol(const StreamBuffer& protocolAndParams)
|
||||
Protocol* protocol;
|
||||
for (protocol = protocols; protocol; protocol = protocol->next)
|
||||
{
|
||||
if (protocol->protocolname.equals(name()))
|
||||
if (protocol->protocolname.startswith(name()))
|
||||
// constructor also replaces parameters
|
||||
return new Protocol(*protocol, name, 0);
|
||||
}
|
||||
error("Protocol '%s' not found in protocol file '%s'\n",
|
||||
name(), filename());
|
||||
protocolAndParams(), filename());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -292,18 +280,18 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
|
||||
// end of protocol or handler definition
|
||||
return true;
|
||||
}
|
||||
errorMsg("Stray '}' in global context\n");
|
||||
error(line, filename(), "Stray '}' in global context\n");
|
||||
return false;
|
||||
}
|
||||
if (strchr("{=", token[0]))
|
||||
{
|
||||
errorMsg("Expect name before '%c'\n", token[0]);
|
||||
error(line, filename(), "Expect name before '%c'\n", token[0]);
|
||||
return false;
|
||||
}
|
||||
do op = readChar(); while (op == ' '); // what comes after token?
|
||||
if (op == EOF)
|
||||
{
|
||||
errorMsg("Unexpected end of file after: %s\n", token());
|
||||
error(line, filename(), "Unexpected end of file after: %s\n", token());
|
||||
return false;
|
||||
}
|
||||
if (op == '=')
|
||||
@ -311,20 +299,20 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
|
||||
// variable assignment
|
||||
if (isHandlerContext(protocol, commands))
|
||||
{
|
||||
errorMsg("Variables are not allowed in handlers: %s\n",
|
||||
error(line, filename(), "Variables are not allowed in handlers: %s\n",
|
||||
token());
|
||||
return false;
|
||||
}
|
||||
if (token[0] == '@' || (token[0] >= '0' && token[0] <= '9'))
|
||||
{
|
||||
errorMsg("Variable name cannot start with '%c': %s\n",
|
||||
error(line, filename(), "Variable name cannot start with '%c': %s\n",
|
||||
token[0], token());
|
||||
return false;
|
||||
}
|
||||
if (!parseAssignment(token(), protocol))
|
||||
{
|
||||
line = startline;
|
||||
errorMsg("in variable assignment '%s = ...'\n", token());
|
||||
error(line, filename(), "in variable assignment '%s = ...'\n", token());
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
@ -336,7 +324,7 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
|
||||
{
|
||||
if (isHandlerContext(protocol, commands))
|
||||
{
|
||||
errorMsg("Handlers are not allowed in handlers: %s\n",
|
||||
error(line, filename(), "Handlers are not allowed in handlers: %s\n",
|
||||
token());
|
||||
return false;
|
||||
}
|
||||
@ -345,7 +333,7 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
|
||||
if (!parseProtocol(protocol, handler))
|
||||
{
|
||||
line = startline;
|
||||
errorMsg("in handler '%s'\n", token());
|
||||
error(line, filename(), "in handler '%s'\n", token());
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
@ -353,16 +341,16 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
|
||||
// protocol definition
|
||||
if (!isGlobalContext(commands))
|
||||
{
|
||||
errorMsg("Definition of '%s' not in global context (missing '}' ?)\n",
|
||||
error(line, filename(), "Definition of '%s' not in global context (missing '}' ?)\n",
|
||||
token());
|
||||
return false;
|
||||
}
|
||||
Protocol** ppP;
|
||||
for (ppP = &protocols; *ppP; ppP = &(*ppP)->next)
|
||||
{
|
||||
if ((*ppP)->protocolname.equals(token()))
|
||||
if ((*ppP)->protocolname.startswith(token()))
|
||||
{
|
||||
errorMsg("Protocol '%s' redefined\n", token());
|
||||
error(line, filename(), "Protocol '%s' redefined\n", token());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -370,7 +358,7 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
|
||||
if (!parseProtocol(*pP, pP->commands))
|
||||
{
|
||||
line = startline;
|
||||
errorMsg("in protocol '%s'\n", token());
|
||||
error(line, filename(), "in protocol '%s'\n", token());
|
||||
delete pP;
|
||||
return false;
|
||||
}
|
||||
@ -381,7 +369,7 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
|
||||
// Must be a command or a protocol reference.
|
||||
if (isGlobalContext(commands))
|
||||
{
|
||||
errorMsg("Expect '=' or '{' instead of '%c' after '%s'\n",
|
||||
error(line, filename(), "Expect '=' or '{' instead of '%c' after '%s'\n",
|
||||
op, token());
|
||||
return false;
|
||||
}
|
||||
@ -392,7 +380,7 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
|
||||
Protocol* p;
|
||||
for (p = protocols; p; p = p->next)
|
||||
{
|
||||
if (p->protocolname.equals(token()))
|
||||
if (p->protocolname.startswith(token()))
|
||||
{
|
||||
commands->append(*p->commands);
|
||||
break;
|
||||
@ -406,7 +394,7 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
|
||||
if (parseValue(*commands, true) == false)
|
||||
{
|
||||
line = startline;
|
||||
errorMsg("after command '%s'\n", token());
|
||||
error(line, filename(), "after command '%s'\n", token());
|
||||
return false;
|
||||
}
|
||||
debug("parseProtocol: command '%s'\n", (*commands).expand()());
|
||||
@ -486,7 +474,7 @@ Each time newline is read, line is incremented.
|
||||
c = getc(file);
|
||||
if (c != '}')
|
||||
{
|
||||
errorMsg("Expect '}' instead of '%c' after: %s\n",
|
||||
error(line, filename(), "Expect '}' instead of '%c' after: %s\n",
|
||||
c, buffer(token));
|
||||
return false;
|
||||
}
|
||||
@ -495,12 +483,12 @@ Each time newline is read, line is incremented.
|
||||
}
|
||||
if (c == EOF)
|
||||
{
|
||||
errorMsg("Unexpected end of file after '$'\n");
|
||||
error(line, filename(), "Unexpected end of file after '$'\n");
|
||||
return false;
|
||||
}
|
||||
if (strchr (specialchars, c))
|
||||
{
|
||||
errorMsg("Unexpected '%c' after '$'\n,", c);
|
||||
error(line, filename(), "Unexpected '%c' after '$'\n,", c);
|
||||
return false;
|
||||
}
|
||||
// variable like $xyz handled as word token
|
||||
@ -519,7 +507,7 @@ Each time newline is read, line is incremented.
|
||||
{
|
||||
if (c == EOF || c == '\n')
|
||||
{
|
||||
errorMsg("Unterminated quoted string: %s\n",
|
||||
error(line, filename(), "Unterminated quoted string: %s\n",
|
||||
buffer(token));
|
||||
return false;
|
||||
}
|
||||
@ -532,7 +520,7 @@ Each time newline is read, line is incremented.
|
||||
break;
|
||||
}
|
||||
buffer.append(c);
|
||||
if (c == quote && buffer[-1] != '\\')
|
||||
if (c == quote && buffer[-2] != '\\')
|
||||
{
|
||||
quote = false;
|
||||
break;
|
||||
@ -547,7 +535,7 @@ Each time newline is read, line is incremented.
|
||||
// end of file
|
||||
if (!eofAllowed)
|
||||
{
|
||||
errorMsg("Unexpected end of file\n");
|
||||
error(line, filename(), "Unexpected end of file\n");
|
||||
return false;
|
||||
}
|
||||
buffer.append('\0');
|
||||
@ -620,7 +608,7 @@ parseValue(StreamBuffer& buffer, bool lazy)
|
||||
}
|
||||
if (c == '{' || c == '=')
|
||||
{
|
||||
errorMsg("Unexpected '%c' (missing ';' or '\"' ?)\n", c);
|
||||
error(line, filename(), "Unexpected '%c' (missing ';' or '\"' ?)\n", c);
|
||||
return false;
|
||||
}
|
||||
if (strchr (";}", c))
|
||||
@ -646,7 +634,7 @@ printString(StreamBuffer& buffer, const char* s)
|
||||
switch (*s)
|
||||
{
|
||||
case esc:
|
||||
buffer.printf("\\x%02x", (*++s) & 0xff);
|
||||
buffer.print("\\x%02x", (*++s) & 0xff);
|
||||
break;
|
||||
case '\r':
|
||||
buffer.append("\\r");
|
||||
@ -657,6 +645,9 @@ printString(StreamBuffer& buffer, const char* s)
|
||||
case skip:
|
||||
buffer.append("\\?");
|
||||
break;
|
||||
case whitespace:
|
||||
buffer.append("\\_");
|
||||
break;
|
||||
case '"':
|
||||
buffer.append("\\\"");
|
||||
break;
|
||||
@ -664,7 +655,7 @@ printString(StreamBuffer& buffer, const char* s)
|
||||
buffer.append("\\\\");
|
||||
break;
|
||||
case format_field:
|
||||
buffer.printf("%%(%s)", ++s);
|
||||
buffer.print("%%(%s)", ++s);
|
||||
while (*s++);
|
||||
s += extract<unsigned short>(s); // skip fieldaddress
|
||||
goto format;
|
||||
@ -679,7 +670,7 @@ format: {
|
||||
continue;
|
||||
default:
|
||||
if ((*s & 0x7f) < 0x20 || (*s & 0x7f) == 0x7f)
|
||||
buffer.printf("\\x%02x", *s & 0xff);
|
||||
buffer.print("\\x%02x", *s & 0xff);
|
||||
else
|
||||
buffer.append(*s);
|
||||
}
|
||||
@ -751,7 +742,7 @@ Protocol(const Protocol& p, StreamBuffer& name, int _line)
|
||||
int i;
|
||||
const char* nextparameter;
|
||||
parameter[0] = protocolname();
|
||||
for (i = 0; i < 10; i++)
|
||||
for (i = 0; i < 9; i++)
|
||||
{
|
||||
debug("StreamProtocolParser::Protocol::Protocol $%d=\"%s\"\n",
|
||||
i, parameter[i]);
|
||||
@ -768,18 +759,6 @@ StreamProtocolParser::Protocol::
|
||||
delete next;
|
||||
}
|
||||
|
||||
void StreamProtocolParser::Protocol::
|
||||
errorMsg(int l, const char* fmt, ...)
|
||||
{
|
||||
char fmt2[200];
|
||||
sprintf (fmt2, "'%s' line %d: %s",
|
||||
filename(), line, fmt);
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
StreamVError(fmt2, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void StreamProtocolParser::Protocol::
|
||||
report()
|
||||
{
|
||||
@ -811,7 +790,7 @@ createVariable(const char* name, int linenr)
|
||||
Variable** ppV;
|
||||
for (ppV = &variables; *ppV; ppV = &(*ppV)->next)
|
||||
{
|
||||
if ((*ppV)->name.equals(name))
|
||||
if ((*ppV)->name.startswith(name))
|
||||
{
|
||||
(*ppV)->line = linenr;
|
||||
return &(*ppV)->value;
|
||||
@ -829,7 +808,7 @@ getVariable(const char* name)
|
||||
|
||||
for (pV = variables; pV; pV = pV->next)
|
||||
{
|
||||
if (pV->name.equals(name))
|
||||
if (pV->name.startswith(name))
|
||||
{
|
||||
pV->used = true;
|
||||
return pV;
|
||||
@ -847,12 +826,12 @@ getNumberVariable(const char* varname, unsigned long& value, unsigned long max)
|
||||
if (!compileNumber(value, source, max))
|
||||
{
|
||||
int linenr = getLineNumber(source);
|
||||
errorMsg(linenr, "in variable %s\n", varname);
|
||||
error(linenr, filename(), "in variable %s\n", varname);
|
||||
return false;
|
||||
}
|
||||
if (source != pvar->value.end())
|
||||
{
|
||||
errorMsg(getLineNumber(source),
|
||||
error(getLineNumber(source), filename(),
|
||||
"Garbage in variable '%s' after numeric value %ld: %s\n",
|
||||
varname, value, source);
|
||||
return false;
|
||||
@ -867,7 +846,7 @@ getEnumVariable(const char* varname, unsigned short& value, const char** enumstr
|
||||
if (!pvar) return true;
|
||||
for (value = 0; enumstrings[value]; value++)
|
||||
{
|
||||
if (pvar->value.equals(enumstrings[value]))
|
||||
if (pvar->value.startswith(enumstrings[value]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -925,13 +904,14 @@ getCommands(const char* handlername,StreamBuffer& code, Client* client)
|
||||
{
|
||||
if (handlername)
|
||||
{
|
||||
errorMsg(pvar->line,
|
||||
error(pvar->line, filename(),
|
||||
"in handler '%s'\n", handlername);
|
||||
errorMsg(variables->line,
|
||||
error(variables->line, filename(),
|
||||
"used by protocol '%s'\n", protocolname());
|
||||
return false;
|
||||
}
|
||||
errorMsg(pvar->line, "in protocol '%s'\n", protocolname());
|
||||
error(pvar->line, filename(),
|
||||
"in protocol '%s'\n", protocolname());
|
||||
return false;
|
||||
}
|
||||
debug("commands %s: %s\n", handlername, pvar->value.expand()());
|
||||
@ -955,7 +935,7 @@ replaceVariable(StreamBuffer& buffer, const char* varname)
|
||||
const char* p = parameter[*varname-'0'];
|
||||
if (!p)
|
||||
{
|
||||
errorMsg(linenr,
|
||||
error(linenr, filename(),
|
||||
"Missing value for parameter $%c\n", *varname);
|
||||
return false;
|
||||
}
|
||||
@ -981,7 +961,7 @@ replaceVariable(StreamBuffer& buffer, const char* varname)
|
||||
const Variable* v = getVariable(varname);
|
||||
if (!v)
|
||||
{
|
||||
errorMsg(linenr,
|
||||
error(linenr, filename(),
|
||||
"Undefined variable '%s' referenced\n",
|
||||
varname);
|
||||
return false;
|
||||
@ -1045,7 +1025,7 @@ compileNumber(unsigned long& number, const char*& source, unsigned long max)
|
||||
{
|
||||
debug("StreamProtocolParser::Protocol::compileNumber: %s\n",
|
||||
buffer.expand()());
|
||||
errorMsg(getLineNumber(source),
|
||||
error(getLineNumber(source), filename(),
|
||||
"Unsigned numeric value expected: %s\n", buffer());
|
||||
return false;
|
||||
}
|
||||
@ -1053,7 +1033,7 @@ compileNumber(unsigned long& number, const char*& source, unsigned long max)
|
||||
{
|
||||
debug("StreamProtocolParser::Protocol::compileNumber: %s\n",
|
||||
buffer.expand()());
|
||||
errorMsg(getLineNumber(source),
|
||||
error(getLineNumber(source), filename(),
|
||||
"Garbage after numeric value: %s\n", buffer());
|
||||
return false;
|
||||
}
|
||||
@ -1061,7 +1041,7 @@ compileNumber(unsigned long& number, const char*& source, unsigned long max)
|
||||
{
|
||||
debug("StreamProtocolParser::Protocol::compileNumber: %s\n",
|
||||
buffer.expand()());
|
||||
errorMsg(getLineNumber(source),
|
||||
error(getLineNumber(source), filename(),
|
||||
"Value %s out of range [0...%ld]\n", buffer(), max);
|
||||
return false;
|
||||
}
|
||||
@ -1079,11 +1059,9 @@ compileString(StreamBuffer& buffer, const char*& source,
|
||||
FormatType formatType, Client* client, int quoted)
|
||||
{
|
||||
bool escaped = false;
|
||||
int n;
|
||||
long formatPos[20];
|
||||
int numFormats = 0;
|
||||
int newline = 0;
|
||||
StreamBuffer formatbuffer;
|
||||
int formatpos = buffer.length();
|
||||
line = getLineNumber(source);
|
||||
|
||||
debug("StreamProtocolParser::Protocol::compileString "
|
||||
@ -1094,41 +1072,58 @@ compileString(StreamBuffer& buffer, const char*& source,
|
||||
// 1) read a line from protocol source and code quoted strings,
|
||||
// numerical bytes, tokens, etc, and replace variables and parameters
|
||||
// 2) replace the formats in the line
|
||||
// thus variables can be replaces inside the format info string
|
||||
// thus variables can be replaced inside the format info string
|
||||
|
||||
while (1)
|
||||
{
|
||||
debug("StreamProtocolParser::Protocol::compileString "
|
||||
"buffer so far: %s\n", buffer.expand()());
|
||||
// this is step 2: replacing the formats
|
||||
if (!*source || (newline = getLineNumber(source)) != line)
|
||||
{
|
||||
// compile all formats in this line
|
||||
// We do this here after all variables in this line
|
||||
// have been replaced and after string has been coded.
|
||||
int offs = 0;
|
||||
for (n = 0; n < numFormats; n++)
|
||||
if (formatType != NoFormat)
|
||||
{
|
||||
int pos = formatPos[n] + offs;
|
||||
debug("StreamProtocolParser::Protocol::compileString "
|
||||
"format=\"%s\"\n", buffer.expand(pos)());
|
||||
formatbuffer.clear();
|
||||
const char* p = buffer(pos);
|
||||
if (!compileFormat(formatbuffer, p, formatType, client))
|
||||
int nformats=0;
|
||||
char c;
|
||||
while ((c = buffer[formatpos]) != '\0')
|
||||
{
|
||||
p = buffer(pos);
|
||||
formatbuffer.clear();
|
||||
printString(formatbuffer, p);
|
||||
errorMsg(line, "in format string: \"%s\"\n",
|
||||
formatbuffer());
|
||||
return false;
|
||||
if (c == esc) {
|
||||
// ignore escaped %
|
||||
formatpos+=2;
|
||||
continue;
|
||||
}
|
||||
if (c == '%') {
|
||||
debug("StreamProtocolParser::Protocol::compileString "
|
||||
"format=\"%s\"\n", buffer.expand(formatpos)());
|
||||
nformats++;
|
||||
formatbuffer.clear();
|
||||
const char* p = buffer(formatpos);
|
||||
if (!compileFormat(formatbuffer, p, formatType, client))
|
||||
{
|
||||
p = buffer(formatpos);
|
||||
formatbuffer.clear();
|
||||
printString(formatbuffer, p);
|
||||
error(line, filename(),
|
||||
"in format string: \"%s\"\n",
|
||||
formatbuffer());
|
||||
return false;
|
||||
}
|
||||
int formatlen = p - buffer(formatpos);
|
||||
buffer.replace(formatpos, formatlen, formatbuffer);
|
||||
debug("StreamProtocolParser::Protocol::compileString "
|
||||
"replaced by: \"%s\"\n", buffer.expand(formatpos)());
|
||||
formatpos += formatbuffer.length();
|
||||
continue;
|
||||
}
|
||||
formatpos ++;
|
||||
}
|
||||
int formatlen = p - buffer(pos);
|
||||
buffer.replace(pos, formatlen, formatbuffer);
|
||||
debug("StreamProtocolParser::Protocol::compileString "
|
||||
"replaced by: \"%s\"\n", buffer.expand(pos)());
|
||||
offs += formatbuffer.length() - formatlen;
|
||||
"%d formats found in line %d\n", nformats, line);
|
||||
}
|
||||
if (!*source) break;
|
||||
numFormats = 0;
|
||||
line = newline;
|
||||
}
|
||||
// this is step 1: coding the string
|
||||
@ -1140,74 +1135,60 @@ compileString(StreamBuffer& buffer, const char*& source,
|
||||
if (escaped) // char after \ in quoted text
|
||||
{
|
||||
escaped = false;
|
||||
unsigned int temp;
|
||||
unsigned int c;
|
||||
int n=1;
|
||||
switch (*source)
|
||||
{
|
||||
case '$': // can't be: readToken would have made a token from this
|
||||
errorMsg(line,
|
||||
error(line, filename(),
|
||||
"INTERNAL ERROR: unconverted \\$ in quoted string\n");
|
||||
return false;
|
||||
case '?':
|
||||
if (formatType != ScanFormat)
|
||||
{
|
||||
errorMsg(line,
|
||||
"Quoted \\? only allowed in input: %s\n",
|
||||
source-1);
|
||||
return false;
|
||||
}
|
||||
buffer.append(skip);
|
||||
break;
|
||||
source++;
|
||||
continue;
|
||||
case '_':
|
||||
buffer.append(whitespace);
|
||||
source++;
|
||||
continue;
|
||||
case 'a':
|
||||
buffer.append(7);
|
||||
c=7;
|
||||
break;
|
||||
case 'b':
|
||||
buffer.append(8);
|
||||
c=8;
|
||||
break;
|
||||
case 't':
|
||||
buffer.append(9);
|
||||
c=9;
|
||||
break;
|
||||
case 'n':
|
||||
buffer.append('\n');
|
||||
c='\n';
|
||||
break;
|
||||
case 'r':
|
||||
buffer.append('\r');
|
||||
c='\r';
|
||||
break;
|
||||
case 'e':
|
||||
buffer.append(esc).append(esc);
|
||||
c=esc;
|
||||
break;
|
||||
case '0': // octal numbers (max 3 digits after 0)
|
||||
sscanf (source, "%3o%n", &temp, &n);
|
||||
if (temp > 0xFF)
|
||||
case '0': // octal numbers (max 4 digits)
|
||||
sscanf (source, "%4o%n", &c, &n); //cannot fail
|
||||
if (c > 0xFF)
|
||||
{
|
||||
errorMsg(line,
|
||||
"Octal source %#o does not fit in byte: %s\n",
|
||||
temp, source-1);
|
||||
error(line, filename(),
|
||||
"Octal number %#o does not fit in byte: \"%s\"\n",
|
||||
c, source-1);
|
||||
return false;
|
||||
}
|
||||
if (formatType != NoFormat &&
|
||||
(temp < last_function_code || temp == esc))
|
||||
break;
|
||||
case 'x': // hex numbers (max 2 digits after x)
|
||||
if (sscanf (source+1, "%2x%n", &c, &n) < 1)
|
||||
{
|
||||
buffer.append(esc);
|
||||
}
|
||||
buffer.append(temp);
|
||||
source += n;
|
||||
continue;
|
||||
case 'x': // hex numbers (max 2 digits after 0)
|
||||
if (sscanf (source+1, "%2x%n", &temp, &n) < 1)
|
||||
{
|
||||
errorMsg(line,
|
||||
"Hex digit expected after \\x: %s\n",
|
||||
error(line, filename(),
|
||||
"Hex digit expected after \\x: \"%s\"\n",
|
||||
source-1);
|
||||
return false;
|
||||
}
|
||||
if (formatType != NoFormat &&
|
||||
(temp < last_function_code || temp == esc))
|
||||
{
|
||||
buffer.append(esc);
|
||||
}
|
||||
buffer.append(temp);
|
||||
source += n+1;
|
||||
continue;
|
||||
n++;
|
||||
break;
|
||||
case '1': // decimal numbers (max 3 digits)
|
||||
case '2':
|
||||
case '3':
|
||||
@ -1217,51 +1198,33 @@ compileString(StreamBuffer& buffer, const char*& source,
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
sscanf (source, "%3u%n", &temp, &n);
|
||||
if (temp > 0xFF)
|
||||
sscanf (source, "%3u%n", &c, &n); //cannot fail
|
||||
if (c > 0xFF)
|
||||
{
|
||||
errorMsg(line,
|
||||
"Decimal source %d does not fit in byte: %s\n",
|
||||
temp, source-1);
|
||||
error(line, filename(),
|
||||
"Decimal number %d does not fit in byte: \"%s\"\n",
|
||||
c, source-1);
|
||||
return false;
|
||||
}
|
||||
if (formatType != NoFormat &&
|
||||
(temp < last_function_code || temp == esc))
|
||||
{
|
||||
buffer.append(esc);
|
||||
}
|
||||
buffer.append(temp);
|
||||
source += n;
|
||||
continue;
|
||||
break;
|
||||
default: // escaped literals
|
||||
buffer.append(esc).append(*source);
|
||||
c=*source;
|
||||
}
|
||||
source++;
|
||||
source+=n;
|
||||
if (formatType != NoFormat)
|
||||
{
|
||||
buffer.append(esc);
|
||||
}
|
||||
buffer.append(c);
|
||||
continue;
|
||||
}
|
||||
if (quoted) // look for ending quotes, escapes, and formats
|
||||
if (quoted) // look for ending quotes and escapes
|
||||
{
|
||||
switch (*source)
|
||||
{
|
||||
case '\\': // escape next character
|
||||
escaped = true;
|
||||
break;
|
||||
case '%': // format specifier in string
|
||||
if (formatType != NoFormat)
|
||||
{
|
||||
// format is allowed here
|
||||
// just memorize position here and and do actual coding later
|
||||
// after all variables and parameters have been replaced
|
||||
// so that extra information is ready for format converter
|
||||
if (numFormats+1 == sizeof(formatPos))
|
||||
{
|
||||
errorMsg(line, "Too many formats in line");
|
||||
return false;
|
||||
}
|
||||
formatPos[numFormats++]=buffer.length();
|
||||
buffer.append(*source);
|
||||
break;
|
||||
}
|
||||
case '"':
|
||||
case '\'':
|
||||
if (*source == quoted) // ending quote
|
||||
@ -1305,27 +1268,26 @@ compileString(StreamBuffer& buffer, const char*& source,
|
||||
}
|
||||
// try numeric byte value
|
||||
char* p;
|
||||
int temp = strtol(source, &p, 0);
|
||||
int c = strtol(source, &p, 0);
|
||||
if (p != source)
|
||||
{
|
||||
if (*p != 0)
|
||||
{
|
||||
errorMsg(line,
|
||||
error(line, filename(),
|
||||
"Garbage after numeric source: %s", source);
|
||||
return false;
|
||||
}
|
||||
if (temp > 0xFF || temp < -0x80)
|
||||
if (c > 0xFF || c < -0x80)
|
||||
{
|
||||
errorMsg(line,
|
||||
error(line, filename(),
|
||||
"Value %s does not fit in byte\n", source);
|
||||
return false;
|
||||
}
|
||||
if (formatType != NoFormat &&
|
||||
(temp < last_function_code || temp == esc))
|
||||
if (formatType != NoFormat)
|
||||
{
|
||||
buffer.append(esc);
|
||||
}
|
||||
buffer.append(temp);
|
||||
buffer.append(c);
|
||||
source = p+1+sizeof(int);
|
||||
continue;
|
||||
}
|
||||
@ -1349,6 +1311,7 @@ compileString(StreamBuffer& buffer, const char*& source,
|
||||
{"nl", 0x0A},
|
||||
{"vt", 0x0B},
|
||||
{"ff", 0x0C},
|
||||
{"np", 0x0C},
|
||||
{"cr", 0x0D},
|
||||
{"so", 0x0E},
|
||||
{"si", 0x0F},
|
||||
@ -1371,7 +1334,7 @@ compileString(StreamBuffer& buffer, const char*& source,
|
||||
{"del", 0x7f}
|
||||
};
|
||||
size_t i;
|
||||
char c = 0;
|
||||
c=-1;
|
||||
for (i = 0; i < sizeof(codes)/sizeof(*codes); i++)
|
||||
{
|
||||
if (strcmp(source, codes[i].name) == 0)
|
||||
@ -1379,13 +1342,12 @@ compileString(StreamBuffer& buffer, const char*& source,
|
||||
c = codes[i].code;
|
||||
if (c == skip && formatType != ScanFormat)
|
||||
{
|
||||
errorMsg(line,
|
||||
error(line, filename(),
|
||||
"Use of '%s' only allowed in input formats\n",
|
||||
source);
|
||||
return false;
|
||||
}
|
||||
if (formatType != NoFormat &&
|
||||
(temp < last_function_code || temp == esc))
|
||||
if (formatType != NoFormat)
|
||||
{
|
||||
buffer.append(esc);
|
||||
}
|
||||
@ -1394,10 +1356,10 @@ compileString(StreamBuffer& buffer, const char*& source,
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (c) continue;
|
||||
if (c >= 0) continue;
|
||||
// source may contain a function name
|
||||
errorMsg(line,
|
||||
"Unexpected word: %s\n", source);
|
||||
error(line, filename(),
|
||||
"Unexpected word: \"%s\"\n", source);
|
||||
return false;
|
||||
}
|
||||
debug("StreamProtocolParser::Protocol::compileString buffer=%s\n", buffer.expand()());
|
||||
@ -1432,14 +1394,14 @@ compileFormat(StreamBuffer& buffer, const char*& formatstr,
|
||||
buffer.append(format_field);
|
||||
if (!client)
|
||||
{
|
||||
errorMsg(line,
|
||||
error(line, filename(),
|
||||
"Using fieldname is not possible in this context\n");
|
||||
return false;
|
||||
}
|
||||
const char* fieldnameEnd = strchr(source+=2, ')');
|
||||
if (!fieldnameEnd)
|
||||
{
|
||||
errorMsg(line,
|
||||
error(line, filename(),
|
||||
"Missing ')' after field name\n");
|
||||
return false;
|
||||
}
|
||||
@ -1451,7 +1413,7 @@ compileFormat(StreamBuffer& buffer, const char*& formatstr,
|
||||
StreamBuffer fieldAddress;
|
||||
if (!client->getFieldAddress(buffer(fieldname), fieldAddress))
|
||||
{
|
||||
errorMsg(line,
|
||||
error(line, filename(),
|
||||
"Field '%s' not found\n", buffer(fieldname));
|
||||
return false;
|
||||
}
|
||||
@ -1465,130 +1427,12 @@ compileFormat(StreamBuffer& buffer, const char*& formatstr,
|
||||
buffer.append(format);
|
||||
}
|
||||
const char* formatstart = source + 1;
|
||||
// look for flags
|
||||
streamFormat.flags = 0;
|
||||
bool loop = true;
|
||||
while (loop)
|
||||
{
|
||||
switch (*++source)
|
||||
{
|
||||
case '-':
|
||||
streamFormat.flags |= left_flag;
|
||||
break;
|
||||
case '+':
|
||||
streamFormat.flags |= sign_flag;
|
||||
break;
|
||||
case ' ':
|
||||
streamFormat.flags |= space_flag;
|
||||
break;
|
||||
case '#':
|
||||
streamFormat.flags |= alt_flag;
|
||||
break;
|
||||
case '0':
|
||||
streamFormat.flags |= zero_flag;
|
||||
break;
|
||||
case '*':
|
||||
if (formatType != ScanFormat)
|
||||
{
|
||||
errorMsg(line,
|
||||
"Use of skip modifier '*' "
|
||||
"only allowed in input formats\n");
|
||||
return false;
|
||||
}
|
||||
if (fieldname)
|
||||
{
|
||||
errorMsg(line,
|
||||
"Use of skip modifier '*' not allowed "
|
||||
"together with field name\n");
|
||||
return false;
|
||||
}
|
||||
streamFormat.flags |= skip_flag;
|
||||
break;
|
||||
default:
|
||||
loop = false;
|
||||
}
|
||||
}
|
||||
/*
|
||||
if (formatType != PrintFormat &&
|
||||
streamFormat.flags & (left_flag|sign_flag|space_flag|zero_flag))
|
||||
{
|
||||
errorMsg(line,
|
||||
"Use of format modifiers '-', '+', ' ', '0' "
|
||||
"only allowed in output formats\n");
|
||||
return false;
|
||||
}
|
||||
if (!(~streamFormat.flags & (left_flag|zero_flag)))
|
||||
{
|
||||
errorMsg(line,
|
||||
"Can't use modifiers '-' and '0' together\n");
|
||||
return false;
|
||||
}
|
||||
if (!(~streamFormat.flags & (space_flag|sign_flag)))
|
||||
{
|
||||
errorMsg(line,
|
||||
"Can't use modifiers ' ' and '+' together\n");
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
// look for width
|
||||
unsigned long val;
|
||||
char* p;
|
||||
val = strtoul (source, &p, 10);
|
||||
source = p;
|
||||
if (val > 0xFFFF)
|
||||
{
|
||||
errorMsg(line,
|
||||
"Field width %ld out of range\n", val);
|
||||
return false;
|
||||
}
|
||||
streamFormat.width = (unsigned short)val;
|
||||
// look for prec
|
||||
streamFormat.prec = -1;
|
||||
if (*source == '.')
|
||||
{
|
||||
source++;
|
||||
val = strtoul(source, &p, 10);
|
||||
if (p == source)
|
||||
{
|
||||
debug("source = %s\n", source);
|
||||
errorMsg(line,
|
||||
"Numeric precision field expected after '.'\n");
|
||||
return false;
|
||||
}
|
||||
source = p;
|
||||
if (val > 0x7FFF)
|
||||
{
|
||||
errorMsg(line,
|
||||
"Precision %ld out of range\n", val);
|
||||
return false;
|
||||
}
|
||||
streamFormat.prec = (short)val;
|
||||
}
|
||||
// look for converter
|
||||
streamFormat.conv = *source++;
|
||||
debug("StreamProtocolParser::Protocol::compileFormat: converter='%c'\n",
|
||||
streamFormat.conv);
|
||||
if (!streamFormat.conv || strchr("'\" (.0+-*", streamFormat.conv))
|
||||
{
|
||||
debug("StreamProtocolParser::Protocol::compileFormat: formatstr='%s'\n",
|
||||
formatstr);
|
||||
errorMsg(line,
|
||||
"Missing converter character\n");
|
||||
return false;
|
||||
}
|
||||
StreamFormatConverter* converter;
|
||||
converter = StreamFormatConverter::find(streamFormat.conv);
|
||||
if (!converter)
|
||||
{
|
||||
errorMsg(line,
|
||||
"No converter registered for format '%%%c'\n",
|
||||
streamFormat.conv);
|
||||
return false;
|
||||
}
|
||||
|
||||
// parse format and get info string
|
||||
StreamBuffer infoString;
|
||||
int type = converter->parse(streamFormat, infoString,
|
||||
source, formatType == ScanFormat);
|
||||
int type = StreamFormatConverter::parseFormat(source,
|
||||
formatType, streamFormat, infoString);
|
||||
|
||||
if (!type)
|
||||
{
|
||||
// parsing failed
|
||||
@ -1596,18 +1440,25 @@ compileFormat(StreamBuffer& buffer, const char*& formatstr,
|
||||
}
|
||||
if (type < long_format && type > pseudo_format)
|
||||
{
|
||||
errorMsg(line,
|
||||
error(line, filename(),
|
||||
"Illegal format type %d returned from '%%%c' converter\n",
|
||||
type, streamFormat.conv);
|
||||
return false;
|
||||
}
|
||||
if (type == pseudo_format && fieldname)
|
||||
{
|
||||
errorMsg(line,
|
||||
error(line, filename(),
|
||||
"Fieldname not allowed with pseudo format: '%%(%s)%c'\n",
|
||||
buffer(fieldname), streamFormat.conv);
|
||||
return false;
|
||||
}
|
||||
if (fieldname && streamFormat.flags & skip_flag)
|
||||
{
|
||||
error(line, filename(),
|
||||
"Use of skip modifier '*' not allowed "
|
||||
"together with redirection\n");
|
||||
return false;
|
||||
}
|
||||
streamFormat.type = static_cast<StreamFormatType>(type);
|
||||
if (infoString && infoString[-1] != eos)
|
||||
{
|
||||
@ -1627,8 +1478,10 @@ compileFormat(StreamBuffer& buffer, const char*& formatstr,
|
||||
buffer.append(&streamFormat, sizeof(streamFormat));
|
||||
buffer.append(infoString);
|
||||
|
||||
debug("StreamProtocolParser::Protocol::compileFormat: format.type=%s, infolen=%d\n",
|
||||
StreamFormatTypeStr[streamFormat.type], streamFormat.infolen);
|
||||
debug("StreamProtocolParser::Protocol::compileFormat: format.type=%s, "
|
||||
"infolen=%d infostring=\"%s\"\n",
|
||||
StreamFormatTypeStr[streamFormat.type],
|
||||
streamFormat.infolen, infoString.expand()());
|
||||
formatstr = source; // move pointer after parsed format
|
||||
return true;
|
||||
}
|
||||
@ -1645,13 +1498,13 @@ compileCommands(StreamBuffer& buffer, const char*& source, Client* client)
|
||||
args = source + strlen(source)+1+sizeof(int);
|
||||
if (!client->compileCommand(this, buffer, command, args))
|
||||
{
|
||||
errorMsg(getLineNumber(source),
|
||||
error(getLineNumber(source), filename(),
|
||||
"in command '%s'\n", command);
|
||||
return false;
|
||||
}
|
||||
if (*args)
|
||||
{
|
||||
errorMsg(getLineNumber(source),
|
||||
error(getLineNumber(source), filename(),
|
||||
"Garbage after '%s' command: '%s'\n",
|
||||
command, args);
|
||||
return false;
|
||||
|
@ -32,7 +32,7 @@ public:
|
||||
|
||||
enum Codes
|
||||
{
|
||||
eos = 0, skip, format, format_field, last_function_code
|
||||
eos = 0, skip, whitespace, format, format_field, last_function_code
|
||||
};
|
||||
|
||||
class Client;
|
||||
@ -75,8 +75,6 @@ public:
|
||||
bool compileString(StreamBuffer& buffer, const char*& source,
|
||||
FormatType formatType = NoFormat, Client* = NULL, int quoted = false);
|
||||
bool checkUnused();
|
||||
void errorMsg(int line, const char* fmt, ...)
|
||||
__attribute__ ((format(printf,3,4)));
|
||||
~Protocol();
|
||||
void report();
|
||||
};
|
||||
@ -115,8 +113,6 @@ private:
|
||||
const char* specialchars = NULL, bool eofAllowed = false);
|
||||
bool parseAssignment(const char* variable, Protocol&);
|
||||
bool parseValue(StreamBuffer& buffer, bool lazy = false);
|
||||
void errorMsg(const char* fmt, ...)
|
||||
__attribute__ ((format(printf,2,3)));
|
||||
|
||||
protected:
|
||||
~StreamProtocolParser(); // get rid of cygnus-2.7.2 compiler warning
|
||||
|
@ -23,6 +23,7 @@
|
||||
#define STR2(x) #x
|
||||
#define STR(x) STR2(x)
|
||||
const char StreamVersion [] =
|
||||
"StreamDevice " STR(STREAM_MAJOR) "." STR(STREAM_MINOR)
|
||||
"StreamDevice " STR(STREAM_MAJOR)
|
||||
"." STR(STREAM_MINOR)
|
||||
"." STR(STREAM_PATCHLEVEL)
|
||||
" built " __DATE__ " " __TIME__;
|
||||
|
||||
|
539
src/TimestampConverter.cc
Normal file
@ -0,0 +1,539 @@
|
||||
/***************************************************************
|
||||
* StreamDevice Support *
|
||||
* *
|
||||
* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
|
||||
* (C) 2010 Dirk Zimoch (dirk.zimoch@psi.ch) *
|
||||
* *
|
||||
* This is the time stamp converter of StreamDevice. *
|
||||
* Please refer to the HTML files in ../doc/ for a detailed *
|
||||
* documentation. *
|
||||
* *
|
||||
* If you do any changes in this file, you are not allowed to *
|
||||
* redistribute it any more. If there is a bug or a missing *
|
||||
* feature, send me an email and/or your patch. If I accept *
|
||||
* your changes, they will go to the next release. *
|
||||
* *
|
||||
* DISCLAIMER: If this software breaks something or harms *
|
||||
* someone, it's your problem. *
|
||||
* *
|
||||
***************************************************************/
|
||||
|
||||
#include "StreamFormatConverter.h"
|
||||
#include "StreamError.h"
|
||||
|
||||
#include <time.h>
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define tzset() _tzset()
|
||||
#define timezone _timezone
|
||||
#define localtime_r(timet,tm) localtime_s(tm,timet) /* Windows sucks */
|
||||
#endif
|
||||
|
||||
#ifdef __rtems__
|
||||
#define timezone _timezone
|
||||
#endif
|
||||
|
||||
#ifdef vxWorks
|
||||
int timezone = 0;
|
||||
#define tzset() do {\
|
||||
(void) (sscanf(getenv("TIMEZONE"), "%*[^:]::%d", &timezone) || \
|
||||
sscanf(getenv("TIMEZONE"), "%*[^:]:%*[^:]:%d", &timezone) || \
|
||||
sscanf(getenv("EPICS_TS_MIN_WEST"), "%d", &timezone));\
|
||||
timezone*=60;\
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
class TimestampConverter : public StreamFormatConverter
|
||||
{
|
||||
int parse(const StreamFormat&, StreamBuffer&, const char*&, bool);
|
||||
bool printDouble(const StreamFormat&, StreamBuffer&, double);
|
||||
int scanDouble(const StreamFormat&, const char*, double&);
|
||||
};
|
||||
|
||||
int TimestampConverter::
|
||||
parse(const StreamFormat&, StreamBuffer& info,
|
||||
const char*& source, bool)
|
||||
{
|
||||
unsigned int n;
|
||||
char* c;
|
||||
|
||||
if (*source == '(')
|
||||
{
|
||||
while (*++source != ')')
|
||||
{
|
||||
switch (*source)
|
||||
{
|
||||
case 0:
|
||||
error ("missing ')' after %%T format\n");
|
||||
return false;
|
||||
case esc:
|
||||
info.append(*++source);
|
||||
if (*source == '%') info.append('%');
|
||||
break;
|
||||
case '%':
|
||||
source++;
|
||||
/* look for formatted fractions like %3f */
|
||||
if (isdigit(*source))
|
||||
{
|
||||
n = strtoul(source, &c, 10);
|
||||
if (*c == 'f')
|
||||
{
|
||||
source = c;
|
||||
info.print("%%0%uf", n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* look for nanoseconds %N of %f */
|
||||
if (*source == 'N' || *source == 'f')
|
||||
{
|
||||
info.print("%%09f");
|
||||
break;
|
||||
}
|
||||
/* look for seconds with fractions like %.3S */
|
||||
if (*source == '.')
|
||||
{
|
||||
c = (char*) source+1;
|
||||
n = isdigit(*c) ? strtoul(c, &c, 10) : 9;
|
||||
if (toupper(*c) == 'S')
|
||||
{
|
||||
source = c;
|
||||
info.print("%%%c.%%0%uf", *c, n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* else normal format */
|
||||
info.append('%');
|
||||
default:
|
||||
info.append(*source);
|
||||
}
|
||||
}
|
||||
source++;
|
||||
info.append('\0');
|
||||
}
|
||||
else
|
||||
{
|
||||
info.append("%Y-%m-%d %H:%M:%S").append('\0');
|
||||
}
|
||||
return double_format;
|
||||
}
|
||||
|
||||
bool TimestampConverter::
|
||||
printDouble(const StreamFormat& format, StreamBuffer& output, double value)
|
||||
{
|
||||
struct tm brokenDownTime;
|
||||
char buffer [40];
|
||||
char fracbuffer [15];
|
||||
int length;
|
||||
time_t sec;
|
||||
double frac;
|
||||
int i, n;
|
||||
char* c;
|
||||
char* p;
|
||||
|
||||
sec = (time_t) value;
|
||||
frac = value - sec;
|
||||
localtime_r(&sec, &brokenDownTime);
|
||||
debug ("TimestampConverter::printDouble %f, '%s'\n", value, format.info);
|
||||
length = strftime(buffer, sizeof(buffer), format.info, &brokenDownTime);
|
||||
i = output.length();
|
||||
output.append(buffer, length);
|
||||
|
||||
/* look for fractional seconds */
|
||||
while ((i = output.find("%0",i)) != -1)
|
||||
{
|
||||
n = strtol(output(i+1), &c, 10);
|
||||
if (*c++ != 'f') return false;
|
||||
/* print fractional part */
|
||||
sprintf(fracbuffer, "%.*f", n, frac);
|
||||
p = strchr(fracbuffer, '.')+1;
|
||||
output.replace(i, c-output(i), p);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* many OS don't have strptime or strptime does not fully support
|
||||
all fields, e.g. %z.
|
||||
*/
|
||||
|
||||
static int strmatch(const char*& input, const char** strings, int minlen)
|
||||
{
|
||||
int i;
|
||||
int c;
|
||||
|
||||
for (i=0; strings[i]; i++) {
|
||||
for (c=0; ; c++)
|
||||
{
|
||||
if (strings[i][c] == 0) {
|
||||
input += c;
|
||||
return i;
|
||||
}
|
||||
if (tolower(input[c]) != strings[i][c]) {
|
||||
if (c >= minlen) {
|
||||
input += c;
|
||||
return i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int nummatch(const char*& input, int min, int max)
|
||||
{
|
||||
int i;
|
||||
char *c;
|
||||
|
||||
i = strtol(input, &c, 10);
|
||||
if (c == input) return -10000;
|
||||
if (i < min || i > max) return -10000;
|
||||
input = c;
|
||||
return i;
|
||||
}
|
||||
|
||||
static const char* scantime(const char* input, const char* format, struct tm *tm, unsigned long *ns)
|
||||
{
|
||||
static const char* months[] = {
|
||||
"january", "february", "march", "april", "may", "june",
|
||||
"july", "august", "september", "november", "december", 0 };
|
||||
static const char* ampm[] = {
|
||||
"am", "pm", 0 };
|
||||
|
||||
int i, n;
|
||||
int pm = -1;
|
||||
int century = -1;
|
||||
int zone = 0;
|
||||
|
||||
tzset();
|
||||
zone = timezone/60;
|
||||
debug ("TimestampConverter::scantime: native time zone = %d\n", zone);
|
||||
|
||||
while (*format)
|
||||
{
|
||||
switch (*format)
|
||||
{
|
||||
case '%':
|
||||
debug ("TimestampConverter::scantime: input = '%s'\n", input);
|
||||
format++;
|
||||
startover:
|
||||
switch (*format++)
|
||||
{
|
||||
/* Modifiers (ignore) */
|
||||
case 'E':
|
||||
case 'O':
|
||||
goto startover;
|
||||
/* constants */
|
||||
case 0: /* stray % at end of format string */
|
||||
format--;
|
||||
case '%':
|
||||
if (*input++ != '%') return NULL;
|
||||
break;
|
||||
case 'n':
|
||||
if (*input++ != '\n') return NULL;
|
||||
break;
|
||||
case 't':
|
||||
if (*input++ != '\t') return NULL;
|
||||
break;
|
||||
/* ignored */
|
||||
case 'A': /* day of week name */
|
||||
case 'a':
|
||||
while (isalpha((int)*input)) input++;
|
||||
/* ignore */
|
||||
break;
|
||||
case 'u': /* day of week number (Monday = 1 to Sunday = 7) */
|
||||
case 'w': /* day of week number (Sunday = 0 to Saturday = 6) */
|
||||
i = nummatch(input, 0, 7);
|
||||
if (i < 0)
|
||||
{
|
||||
error ("error parsing day of week: '%.20s'\n", input);
|
||||
return NULL;
|
||||
}
|
||||
debug ("TimestampConverter::scantime: day of week = %d\n", i);
|
||||
/* ignore */
|
||||
break;
|
||||
case 'U': /* week number */
|
||||
case 'W':
|
||||
case 'V':
|
||||
i = nummatch(input, 0, 53);
|
||||
if (i < 0)
|
||||
{
|
||||
error ("error parsing week number: '%.20s'\n", input);
|
||||
return NULL;
|
||||
}
|
||||
debug ("TimestampConverter::scantime: week number = %d\n", i);
|
||||
/* ignore */
|
||||
break;
|
||||
case 'j': /* day of year */
|
||||
i = nummatch(input, 0, 366);
|
||||
if (i < 0)
|
||||
{
|
||||
error ("error parsing day of year: '%.20s'\n", input);
|
||||
return NULL;
|
||||
}
|
||||
debug ("TimestampConverter::scantime: day of year = %d\n", i);
|
||||
/* ignore */
|
||||
break;
|
||||
case 'Z': /* time zone name */
|
||||
while (isalpha((int)*input)) input++;
|
||||
/* ignore */
|
||||
break;
|
||||
/* date */
|
||||
case 'b': /* month */
|
||||
case 'h':
|
||||
case 'B':
|
||||
case 'm':
|
||||
i = strmatch(input, months, 3);
|
||||
if (i < 0)
|
||||
{
|
||||
i = nummatch(input, 1, 12);
|
||||
if (i < 0)
|
||||
{
|
||||
error ("error parsing month: '%.20s'\n", input);
|
||||
return NULL;
|
||||
}
|
||||
i--; /* Jan = 0 */
|
||||
}
|
||||
tm->tm_mon = i;
|
||||
debug ("TimestampConverter::scantime: month = %d (%s)\n", tm->tm_mon+1, months[tm->tm_mon]);
|
||||
break;
|
||||
case 'd': /* day of month */
|
||||
case 'e':
|
||||
i = nummatch(input, 1, 31);
|
||||
if (i < 0)
|
||||
{
|
||||
error ("error parsing day of month: '%.20s'\n", input);
|
||||
return NULL;
|
||||
}
|
||||
tm->tm_mday = i;
|
||||
debug ("TimestampConverter::scantime: day = %d\n", tm->tm_mday);
|
||||
break;
|
||||
case 'Y': /* year */
|
||||
case 'y':
|
||||
i = strtol(input, (char**)&input, 10);
|
||||
if (i < 100)
|
||||
{ /* 2 digit year */
|
||||
if (century == -1) century = (i >= 69);
|
||||
tm->tm_year = i + century * 100; /* 0 = 1900 */
|
||||
}
|
||||
else
|
||||
{ /* 4 digit year */
|
||||
tm->tm_year = i - 1900; /* 0 = 1900 */
|
||||
}
|
||||
debug ("TimestampConverter::scantime: year = %d\n", tm->tm_year + 1900);
|
||||
break;
|
||||
case 'C': /* century */
|
||||
i = nummatch(input, 0, 99);
|
||||
if (i < 0)
|
||||
{
|
||||
error ("error parsing century: '%.20s'\n", input);
|
||||
return NULL;
|
||||
}
|
||||
century = i - 19;
|
||||
tm->tm_year = tm->tm_year%100 + 100 * i; /* 0 = 1900 */
|
||||
debug ("TimestampConverter::scantime: year = %d\n", tm->tm_year + 1900);
|
||||
break;
|
||||
|
||||
/* time */
|
||||
case 'H': /* hour */
|
||||
case 'k':
|
||||
case 'I':
|
||||
case 'l':
|
||||
i = nummatch(input, 0, 23);
|
||||
if (i < 0)
|
||||
{
|
||||
error ("error parsing hour: '%.20s'\n", input);
|
||||
return NULL;
|
||||
}
|
||||
if ((pm == 0) && (i == 12)) i = 0;
|
||||
if ((pm == 1) && (i < 12)) i += 12;
|
||||
tm->tm_hour = i;
|
||||
debug ("TimestampConverter::scantime: hour = %d\n", tm->tm_hour);
|
||||
break;
|
||||
case 'P': /* AM / PM */
|
||||
case 'p':
|
||||
i = strmatch(input, ampm, 1);
|
||||
if (i < 0)
|
||||
{
|
||||
error ("error parsing am/pm: '%.20s'\n", input);
|
||||
return NULL;
|
||||
}
|
||||
pm = i;
|
||||
if ((pm == 0) && (tm->tm_hour == 12)) tm->tm_hour = 0;
|
||||
if ((pm == 1) && (tm->tm_hour < 12)) tm->tm_hour += 12;
|
||||
debug ("TimestampConverter::scantime: %s hour = %d\n", pm?"PM":"AM", tm->tm_hour);
|
||||
break;
|
||||
case 'M': /* minute */
|
||||
i = nummatch(input, 1, 59);
|
||||
if (i < 0)
|
||||
{
|
||||
error ("error parsing minute: '%.20s'\n", input);
|
||||
return NULL;
|
||||
}
|
||||
tm->tm_min = i;
|
||||
debug ("TimestampConverter::scantime: min = %d\n", tm->tm_min);
|
||||
break;
|
||||
case 'S': /* second */
|
||||
i = nummatch(input, 1, 60);
|
||||
if (i < 0)
|
||||
{
|
||||
error ("error parsing week second: '%.20s'\n", input);
|
||||
return NULL;
|
||||
}
|
||||
tm->tm_sec = i;
|
||||
debug ("TimestampConverter::scantime: sec = %d\n", tm->tm_sec);
|
||||
break;
|
||||
case 's': /* second since 1970 */
|
||||
i = strtol(input, (char**)&input, 10);
|
||||
tm->tm_sec = i;
|
||||
tm->tm_mon = -1;
|
||||
tm->tm_isdst = 0;
|
||||
debug ("TimestampConverter::scantime: sec = %d\n", tm->tm_sec);
|
||||
break;
|
||||
case '0': /* fractions of seconds like %09f */
|
||||
n = strtol(format-1, (char**)&format, 10);
|
||||
if (*format++ != 'f') return NULL;
|
||||
debug ("max %d digits fraction in '%s'\n", n, input);
|
||||
i = 0;
|
||||
while (n-- && isdigit(*input))
|
||||
{
|
||||
i *= 10;
|
||||
i += *input++ - '0';
|
||||
}
|
||||
while (i < 100000000) i *= 10;
|
||||
*ns = i;
|
||||
debug ("TimestampConverter::scantime: nanosec = %d, rest '%s'\n", i, input);
|
||||
break;
|
||||
case 'z': /* time zone offset */
|
||||
i = nummatch(input, -2400, 2400);
|
||||
if (i < -2400)
|
||||
{
|
||||
error ("error parsing time zone: '%.20s'\n", input);
|
||||
return NULL;
|
||||
}
|
||||
zone = i / 100 * 60 + i % 100;
|
||||
tm->tm_isdst = 0;
|
||||
debug ("TimestampConverter::scantime: zone = %d\n", zone);
|
||||
break;
|
||||
case '+': /* set time zone in format string */
|
||||
case '-':
|
||||
format--;
|
||||
i = nummatch(format, -2400, 2400);
|
||||
zone = i / 100 * 60 + i % 100;
|
||||
tm->tm_isdst = 0;
|
||||
debug ("TimestampConverter::scantime: zone = %d\n", zone);
|
||||
break;
|
||||
/* shortcuts */
|
||||
case 'c':
|
||||
if ((input = scantime(input, "%a %b %d %H:%M:%S %Y", tm, ns)) == NULL)
|
||||
return NULL;
|
||||
break;
|
||||
case 'D':
|
||||
if ((input = scantime(input, "%m/%d/%y", tm, ns)) == NULL)
|
||||
return NULL;
|
||||
break;
|
||||
case 'F':
|
||||
if ((input = scantime(input, "%Y-%m-%d", tm, ns)) == NULL)
|
||||
return NULL;
|
||||
break;
|
||||
case 'R':
|
||||
if ((input = scantime(input, "%H:%M", tm, ns)) == NULL)
|
||||
return NULL;
|
||||
break;
|
||||
case 'T':
|
||||
if ((input = scantime(input, "%H:%M:%S", tm, ns)) == NULL)
|
||||
return NULL;
|
||||
break;
|
||||
case 'x':
|
||||
if ((input = scantime(input, "%m/%d/%y", tm, ns)) == NULL)
|
||||
return NULL;
|
||||
break;
|
||||
case 'X':
|
||||
case 'r':
|
||||
if ((input = scantime(input, "%I:%M:%S %p", tm, ns)) == NULL)
|
||||
return NULL;
|
||||
break;
|
||||
default:
|
||||
error ("unknown time format %%%s\n", --format);
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
case ' ':
|
||||
format++;
|
||||
while (isspace(*input)) input++;
|
||||
break;
|
||||
default:
|
||||
if (*format++ != *input++)
|
||||
{
|
||||
error("input '%.20s' does not match constant '%.20s'\n", --input, --format);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
zone -= timezone/60;
|
||||
tm->tm_min += zone;
|
||||
tm->tm_hour += tm->tm_min / 60;
|
||||
tm->tm_min %= 60;
|
||||
if (tm->tm_min < 0)
|
||||
{
|
||||
tm->tm_min += 60;
|
||||
tm->tm_hour -= 1;
|
||||
}
|
||||
tm->tm_mday -= tm->tm_hour / 24;
|
||||
tm->tm_hour %= 24;
|
||||
if (tm->tm_hour < 0)
|
||||
{
|
||||
tm->tm_min += 24;
|
||||
tm->tm_mday -= 1;
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int TimestampConverter::
|
||||
scanDouble(const StreamFormat& format, const char* input, double& value)
|
||||
{
|
||||
struct tm brokenDownTime;
|
||||
time_t seconds;
|
||||
unsigned long nanoseconds;
|
||||
const char* end;
|
||||
|
||||
/* Init time stamp with "today" */
|
||||
time (&seconds);
|
||||
localtime_r(&seconds, &brokenDownTime);
|
||||
brokenDownTime.tm_sec = 0;
|
||||
brokenDownTime.tm_min = 0;
|
||||
brokenDownTime.tm_hour = 0;
|
||||
brokenDownTime.tm_yday = 0;
|
||||
brokenDownTime.tm_isdst = -1;
|
||||
nanoseconds = 0;
|
||||
|
||||
end = scantime(input, format.info, &brokenDownTime, &nanoseconds);
|
||||
if (end == NULL) {
|
||||
error ("error parsing time\n");
|
||||
return -1;
|
||||
}
|
||||
if (brokenDownTime.tm_mon == -1) {
|
||||
seconds = brokenDownTime.tm_sec;
|
||||
} else {
|
||||
seconds = mktime(&brokenDownTime);
|
||||
if (seconds == (time_t) -1 && brokenDownTime.tm_yday == 0)
|
||||
{
|
||||
error ("mktime failed for %02d/%02d/%04d %02d:%02d:%02d\n",
|
||||
brokenDownTime.tm_mon+1,
|
||||
brokenDownTime.tm_mday,
|
||||
brokenDownTime.tm_year+1900,
|
||||
brokenDownTime.tm_hour,
|
||||
brokenDownTime.tm_min,
|
||||
brokenDownTime.tm_sec);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
value = seconds + nanoseconds*1e-9;
|
||||
return end-input;
|
||||
}
|
||||
|
||||
RegisterConverter (TimestampConverter, "T");
|
@ -22,7 +22,8 @@
|
||||
#define devStream_h
|
||||
|
||||
#define STREAM_MAJOR 2
|
||||
#define STREAM_MINOR 3
|
||||
#define STREAM_MINOR 5
|
||||
#define STREAM_PATCHLEVEL 12
|
||||
|
||||
#if defined(__vxworks) || defined(vxWorks)
|
||||
#include <vxWorks.h>
|
||||
@ -40,11 +41,11 @@
|
||||
#define INIT_RUN (!interruptAccept)
|
||||
|
||||
#include <epicsVersion.h>
|
||||
#if (EPICS_VERSION == 3 && EPICS_REVISION == 14)
|
||||
#define EPICS_3_14
|
||||
#ifdef BASE_RELEASE
|
||||
#define EPICS_3_13
|
||||
#endif
|
||||
|
||||
#if defined(__cplusplus) && !defined(EPICS_3_14)
|
||||
#if defined(__cplusplus) && defined(EPICS_3_13)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
@ -55,7 +56,7 @@ extern "C" {
|
||||
/* #include <dbFldTypes.h> */
|
||||
#include <dbAccess.h>
|
||||
|
||||
#if defined(__cplusplus) && !defined(EPICS_3_14)
|
||||
#if defined(__cplusplus) && defined(EPICS_3_13)
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -69,24 +70,22 @@ typedef const struct format_s {
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllimport)
|
||||
#endif
|
||||
extern FILE* StreamDebugFile;
|
||||
epicsShareExtern FILE* StreamDebugFile;
|
||||
|
||||
extern const char StreamVersion [];
|
||||
|
||||
typedef long (*streamIoFunction) (dbCommon*, format_t*);
|
||||
|
||||
long streamInit(int after);
|
||||
long streamInitRecord(dbCommon *record, struct link *ioLink,
|
||||
epicsShareExtern long streamInit(int after);
|
||||
epicsShareExtern long streamInitRecord(dbCommon *record,
|
||||
const struct link *ioLink,
|
||||
streamIoFunction readData, streamIoFunction writeData);
|
||||
long streamReport(int interest);
|
||||
long streamReadWrite(dbCommon *record);
|
||||
long streamGetIointInfo(int cmd, dbCommon *record, IOSCANPVT *ppvt);
|
||||
long streamPrintf(dbCommon *record, format_t *format, ...);
|
||||
long streamScanSep(dbCommon *record);
|
||||
long streamScanfN(dbCommon *record, format_t *format,
|
||||
epicsShareExtern long streamReport(int interest);
|
||||
epicsShareExtern long streamReadWrite(dbCommon *record);
|
||||
epicsShareExtern long streamGetIointInfo(int cmd,
|
||||
dbCommon *record, IOSCANPVT *ppvt);
|
||||
epicsShareExtern long streamPrintf(dbCommon *record, format_t *format, ...);
|
||||
epicsShareExtern long streamScanfN(dbCommon *record, format_t *format,
|
||||
void*, size_t maxStringSize);
|
||||
|
||||
/* backward compatibility stuff */
|
||||
@ -99,7 +98,7 @@ long streamScanfN(dbCommon *record, format_t *format,
|
||||
#define devStreamGetIointInfo streamGetIointInfo
|
||||
#define devStreamPrintf streamPrintf
|
||||
#define devStreamPrintSep(record) (0)
|
||||
#define devStreamScanSep streamScanSep
|
||||
#define devStreamScanSep (0)
|
||||
#define devStreamScanf(record, format, value) \
|
||||
streamScanfN(record, format, value, MAX_STRING_SIZE)
|
||||
#define streamScanf(record, format, value) \
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <stdlib.h>
|
||||
#include "devStream.h"
|
||||
#include <aaiRecord.h>
|
||||
#include <errlog.h>
|
||||
#include <epicsExport.h>
|
||||
|
||||
static long readData (dbCommon *record, format_t *format)
|
||||
@ -43,10 +44,10 @@ static long readData (dbCommon *record, format_t *format)
|
||||
switch (aai->ftvl)
|
||||
{
|
||||
case DBF_DOUBLE:
|
||||
((double *)aai->bptr)[aai->nord] = dval;
|
||||
((epicsFloat64 *)aai->bptr)[aai->nord] = (epicsFloat64)dval;
|
||||
break;
|
||||
case DBF_FLOAT:
|
||||
((float *)aai->bptr)[aai->nord] = (float)dval;
|
||||
((epicsFloat32 *)aai->bptr)[aai->nord] = (epicsFloat32)dval;
|
||||
break;
|
||||
default:
|
||||
errlogSevPrintf (errlogFatal,
|
||||
@ -66,23 +67,23 @@ static long readData (dbCommon *record, format_t *format)
|
||||
switch (aai->ftvl)
|
||||
{
|
||||
case DBF_DOUBLE:
|
||||
((double *)aai->bptr)[aai->nord] = lval;
|
||||
((epicsFloat64 *)aai->bptr)[aai->nord] = (epicsFloat64)lval;
|
||||
break;
|
||||
case DBF_FLOAT:
|
||||
((float *)aai->bptr)[aai->nord] = (float)lval;
|
||||
((epicsFloat32 *)aai->bptr)[aai->nord] = (epicsFloat32)lval;
|
||||
break;
|
||||
case DBF_LONG:
|
||||
case DBF_ULONG:
|
||||
((long *)aai->bptr)[aai->nord] = lval;
|
||||
((epicsInt32 *)aai->bptr)[aai->nord] = (epicsInt32)lval;
|
||||
break;
|
||||
case DBF_SHORT:
|
||||
case DBF_USHORT:
|
||||
case DBF_ENUM:
|
||||
((short *)aai->bptr)[aai->nord] = (short)lval;
|
||||
((epicsInt16 *)aai->bptr)[aai->nord] = (epicsInt16)lval;
|
||||
break;
|
||||
case DBF_CHAR:
|
||||
case DBF_UCHAR:
|
||||
((char *)aai->bptr)[aai->nord] = (char)lval;
|
||||
((epicsInt8 *)aai->bptr)[aai->nord] = (epicsInt8)lval;
|
||||
break;
|
||||
default:
|
||||
errlogSevPrintf (errlogFatal,
|
||||
@ -156,29 +157,29 @@ static long writeData (dbCommon *record, format_t *format)
|
||||
switch (aai->ftvl)
|
||||
{
|
||||
case DBF_DOUBLE:
|
||||
dval = ((double *)aai->bptr)[nowd];
|
||||
dval = ((epicsFloat64 *)aai->bptr)[nowd];
|
||||
break;
|
||||
case DBF_FLOAT:
|
||||
dval = ((float *)aai->bptr)[nowd];
|
||||
dval = ((epicsFloat32 *)aai->bptr)[nowd];
|
||||
break;
|
||||
case DBF_LONG:
|
||||
dval = ((long *)aai->bptr)[nowd];
|
||||
dval = ((epicsInt32 *)aai->bptr)[nowd];
|
||||
break;
|
||||
case DBF_ULONG:
|
||||
dval = ((unsigned long *)aai->bptr)[nowd];
|
||||
dval = ((epicsUInt32 *)aai->bptr)[nowd];
|
||||
break;
|
||||
case DBF_SHORT:
|
||||
dval = ((short *)aai->bptr)[nowd];
|
||||
dval = ((epicsInt16 *)aai->bptr)[nowd];
|
||||
break;
|
||||
case DBF_USHORT:
|
||||
case DBF_ENUM:
|
||||
dval = ((unsigned short *)aai->bptr)[nowd];
|
||||
dval = ((epicsUInt16 *)aai->bptr)[nowd];
|
||||
break;
|
||||
case DBF_CHAR:
|
||||
dval = ((char *)aai->bptr)[nowd];
|
||||
dval = ((epicsInt8 *)aai->bptr)[nowd];
|
||||
break;
|
||||
case DBF_UCHAR:
|
||||
dval = ((unsigned char *)aai->bptr)[nowd];
|
||||
dval = ((epicsUInt8 *)aai->bptr)[nowd];
|
||||
break;
|
||||
default:
|
||||
errlogSevPrintf (errlogFatal,
|
||||
@ -196,21 +197,23 @@ static long writeData (dbCommon *record, format_t *format)
|
||||
switch (aai->ftvl)
|
||||
{
|
||||
case DBF_LONG:
|
||||
lval = ((epicsInt32 *)aai->bptr)[nowd];
|
||||
break;
|
||||
case DBF_ULONG:
|
||||
lval = ((long *)aai->bptr)[nowd];
|
||||
lval = ((epicsUInt32 *)aai->bptr)[nowd];
|
||||
break;
|
||||
case DBF_SHORT:
|
||||
lval = ((short *)aai->bptr)[nowd];
|
||||
lval = ((epicsInt16 *)aai->bptr)[nowd];
|
||||
break;
|
||||
case DBF_USHORT:
|
||||
case DBF_ENUM:
|
||||
lval = ((unsigned short *)aai->bptr)[nowd];
|
||||
lval = ((epicsUInt16 *)aai->bptr)[nowd];
|
||||
break;
|
||||
case DBF_CHAR:
|
||||
lval = ((char *)aai->bptr)[nowd];
|
||||
lval = ((epicsInt8 *)aai->bptr)[nowd];
|
||||
break;
|
||||
case DBF_UCHAR:
|
||||
lval = ((unsigned char *)aai->bptr)[nowd];
|
||||
lval = ((epicsUInt8 *)aai->bptr)[nowd];
|
||||
break;
|
||||
default:
|
||||
errlogSevPrintf (errlogFatal,
|
||||
@ -280,7 +283,8 @@ static long initRecord (dbCommon *record)
|
||||
record->name);
|
||||
return ERROR;
|
||||
}
|
||||
return streamInitRecord (record, &aai->inp, readData, writeData);
|
||||
return streamInitRecord (record, &aai->inp, readData, writeData) == ERROR ?
|
||||
ERROR : OK;
|
||||
}
|
||||
|
||||
struct {
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <stdlib.h>
|
||||
#include "devStream.h"
|
||||
#include <aaoRecord.h>
|
||||
#include <errlog.h>
|
||||
#include <epicsExport.h>
|
||||
|
||||
static long readData (dbCommon *record, format_t *format)
|
||||
@ -43,10 +44,10 @@ static long readData (dbCommon *record, format_t *format)
|
||||
switch (aao->ftvl)
|
||||
{
|
||||
case DBF_DOUBLE:
|
||||
((double *)aao->bptr)[aao->nord] = dval;
|
||||
((epicsFloat64 *)aao->bptr)[aao->nord] = (epicsFloat64)dval;
|
||||
break;
|
||||
case DBF_FLOAT:
|
||||
((float *)aao->bptr)[aao->nord] = (float)dval;
|
||||
((epicsFloat32 *)aao->bptr)[aao->nord] = (epicsFloat32)dval;
|
||||
break;
|
||||
default:
|
||||
errlogSevPrintf (errlogFatal,
|
||||
@ -66,23 +67,23 @@ static long readData (dbCommon *record, format_t *format)
|
||||
switch (aao->ftvl)
|
||||
{
|
||||
case DBF_DOUBLE:
|
||||
((double *)aao->bptr)[aao->nord] = lval;
|
||||
((epicsFloat64 *)aao->bptr)[aao->nord] = (epicsFloat64)lval;
|
||||
break;
|
||||
case DBF_FLOAT:
|
||||
((float *)aao->bptr)[aao->nord] = (float)lval;
|
||||
((epicsFloat32 *)aao->bptr)[aao->nord] = (epicsFloat32)lval;
|
||||
break;
|
||||
case DBF_LONG:
|
||||
case DBF_ULONG:
|
||||
((long *)aao->bptr)[aao->nord] = lval;
|
||||
((epicsInt32 *)aao->bptr)[aao->nord] = (epicsInt32)lval;
|
||||
break;
|
||||
case DBF_SHORT:
|
||||
case DBF_USHORT:
|
||||
case DBF_ENUM:
|
||||
((short *)aao->bptr)[aao->nord] = (short)lval;
|
||||
((epicsInt16 *)aao->bptr)[aao->nord] = (epicsInt16)lval;
|
||||
break;
|
||||
case DBF_CHAR:
|
||||
case DBF_UCHAR:
|
||||
((char *)aao->bptr)[aao->nord] = (char)lval;
|
||||
((epicsInt8 *)aao->bptr)[aao->nord] = (epicsInt8)lval;
|
||||
break;
|
||||
default:
|
||||
errlogSevPrintf (errlogFatal,
|
||||
@ -156,29 +157,29 @@ static long writeData (dbCommon *record, format_t *format)
|
||||
switch (aao->ftvl)
|
||||
{
|
||||
case DBF_DOUBLE:
|
||||
dval = ((double *)aao->bptr)[nowd];
|
||||
dval = ((epicsFloat64 *)aao->bptr)[nowd];
|
||||
break;
|
||||
case DBF_FLOAT:
|
||||
dval = ((float *)aao->bptr)[nowd];
|
||||
dval = ((epicsFloat32 *)aao->bptr)[nowd];
|
||||
break;
|
||||
case DBF_LONG:
|
||||
dval = ((long *)aao->bptr)[nowd];
|
||||
dval = ((epicsInt32 *)aao->bptr)[nowd];
|
||||
break;
|
||||
case DBF_ULONG:
|
||||
dval = ((unsigned long *)aao->bptr)[nowd];
|
||||
dval = ((epicsUInt32 *)aao->bptr)[nowd];
|
||||
break;
|
||||
case DBF_SHORT:
|
||||
dval = ((short *)aao->bptr)[nowd];
|
||||
dval = ((epicsInt16 *)aao->bptr)[nowd];
|
||||
break;
|
||||
case DBF_USHORT:
|
||||
case DBF_ENUM:
|
||||
dval = ((unsigned short *)aao->bptr)[nowd];
|
||||
dval = ((epicsUInt16 *)aao->bptr)[nowd];
|
||||
break;
|
||||
case DBF_CHAR:
|
||||
dval = ((char *)aao->bptr)[nowd];
|
||||
dval = ((epicsInt8 *)aao->bptr)[nowd];
|
||||
break;
|
||||
case DBF_UCHAR:
|
||||
dval = ((unsigned char *)aao->bptr)[nowd];
|
||||
dval = ((epicsUInt8 *)aao->bptr)[nowd];
|
||||
break;
|
||||
default:
|
||||
errlogSevPrintf (errlogFatal,
|
||||
@ -196,21 +197,23 @@ static long writeData (dbCommon *record, format_t *format)
|
||||
switch (aao->ftvl)
|
||||
{
|
||||
case DBF_LONG:
|
||||
lval = ((epicsInt32 *)aao->bptr)[nowd];
|
||||
break;
|
||||
case DBF_ULONG:
|
||||
lval = ((long *)aao->bptr)[nowd];
|
||||
lval = ((epicsUInt32 *)aao->bptr)[nowd];
|
||||
break;
|
||||
case DBF_SHORT:
|
||||
lval = ((short *)aao->bptr)[nowd];
|
||||
lval = ((epicsInt16 *)aao->bptr)[nowd];
|
||||
break;
|
||||
case DBF_USHORT:
|
||||
case DBF_ENUM:
|
||||
lval = ((unsigned short *)aao->bptr)[nowd];
|
||||
lval = ((epicsUInt16 *)aao->bptr)[nowd];
|
||||
break;
|
||||
case DBF_CHAR:
|
||||
lval = ((char *)aao->bptr)[nowd];
|
||||
lval = ((epicsInt8 *)aao->bptr)[nowd];
|
||||
break;
|
||||
case DBF_UCHAR:
|
||||
lval = ((unsigned char *)aao->bptr)[nowd];
|
||||
lval = ((epicsUInt8 *)aao->bptr)[nowd];
|
||||
break;
|
||||
default:
|
||||
errlogSevPrintf (errlogFatal,
|
||||
@ -280,7 +283,8 @@ static long initRecord (dbCommon *record)
|
||||
record->name);
|
||||
return ERROR;
|
||||
}
|
||||
return streamInitRecord (record, &aao->out, readData, writeData);
|
||||
return streamInitRecord (record, &aao->out, readData, writeData) == ERROR ?
|
||||
ERROR : OK;
|
||||
}
|
||||
|
||||
struct {
|
||||
|
@ -25,12 +25,12 @@
|
||||
static long readData (dbCommon *record, format_t *format)
|
||||
{
|
||||
aiRecord *ai = (aiRecord *) record;
|
||||
double val;
|
||||
|
||||
switch (format->type)
|
||||
{
|
||||
case DBF_DOUBLE:
|
||||
{
|
||||
double val;
|
||||
if (streamScanf (record, format, &val)) return ERROR;
|
||||
if (ai->aslo != 0.0) val *= ai->aslo;
|
||||
val += ai->aoff;
|
||||
@ -43,7 +43,10 @@ static long readData (dbCommon *record, format_t *format)
|
||||
}
|
||||
case DBF_LONG:
|
||||
{
|
||||
return streamScanf (record, format, &ai->rval);
|
||||
long rval;
|
||||
if (streamScanf (record, format, &rval)) return ERROR;
|
||||
ai->rval = rval;
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
return ERROR;
|
||||
@ -64,7 +67,7 @@ static long writeData (dbCommon *record, format_t *format)
|
||||
}
|
||||
case DBF_LONG:
|
||||
{
|
||||
return streamPrintf (record, format, ai->rval);
|
||||
return streamPrintf (record, format, (long) ai->rval);
|
||||
}
|
||||
}
|
||||
return ERROR;
|
||||
@ -74,7 +77,8 @@ static long initRecord (dbCommon *record)
|
||||
{
|
||||
aiRecord *ai = (aiRecord *) record;
|
||||
|
||||
return streamInitRecord (record, &ai->inp, readData, writeData);
|
||||
return streamInitRecord (record, &ai->inp, readData, writeData) == ERROR ?
|
||||
ERROR : OK;
|
||||
}
|
||||
|
||||
struct {
|
||||
|
@ -51,12 +51,12 @@ static long readData (dbCommon *record, format_t *format)
|
||||
static long writeData (dbCommon *record, format_t *format)
|
||||
{
|
||||
aoRecord *ao = (aoRecord *) record;
|
||||
double val;
|
||||
|
||||
switch (format->type)
|
||||
{
|
||||
case DBF_DOUBLE:
|
||||
{
|
||||
double val;
|
||||
if (INIT_RUN) val = ao->val;
|
||||
else val = ao->oval;
|
||||
val -= ao->aoff;
|
||||
@ -65,7 +65,7 @@ static long writeData (dbCommon *record, format_t *format)
|
||||
}
|
||||
case DBF_LONG:
|
||||
{
|
||||
return streamPrintf (record, format, ao->rval);
|
||||
return streamPrintf (record, format, (long) ao->rval);
|
||||
}
|
||||
}
|
||||
return ERROR;
|
||||
|
@ -90,7 +90,8 @@ static long initRecord (dbCommon *record)
|
||||
{
|
||||
biRecord *bi = (biRecord *) record;
|
||||
|
||||
return streamInitRecord (record, &bi->inp, readData, writeData);
|
||||
return streamInitRecord (record, &bi->inp, readData, writeData) == ERROR ?
|
||||
ERROR : OK;
|
||||
}
|
||||
|
||||
struct {
|
||||
|
@ -68,7 +68,8 @@ static long initRecord (dbCommon *record)
|
||||
{
|
||||
calcoutRecord *co = (calcoutRecord *) record;
|
||||
|
||||
return streamInitRecord (record, &co->out, readData, writeData);
|
||||
return streamInitRecord (record, &co->out, readData, writeData) == ERROR ?
|
||||
ERROR : OK;
|
||||
}
|
||||
|
||||
struct {
|
||||
|
@ -28,7 +28,10 @@ static long readData (dbCommon *record, format_t *format)
|
||||
|
||||
if (format->type == DBF_LONG || format->type == DBF_ENUM)
|
||||
{
|
||||
return streamScanf (record, format, &li->val);
|
||||
long val;
|
||||
if (streamScanf (record, format, &val)) return ERROR;
|
||||
li->val = val;
|
||||
return OK;
|
||||
}
|
||||
return ERROR;
|
||||
}
|
||||
@ -39,7 +42,7 @@ static long writeData (dbCommon *record, format_t *format)
|
||||
|
||||
if (format->type == DBF_LONG || format->type == DBF_ENUM)
|
||||
{
|
||||
return streamPrintf (record, format, li->val);
|
||||
return streamPrintf (record, format, (long) li->val);
|
||||
}
|
||||
return ERROR;
|
||||
}
|
||||
@ -48,7 +51,8 @@ static long initRecord (dbCommon *record)
|
||||
{
|
||||
longinRecord *li = (longinRecord *) record;
|
||||
|
||||
return streamInitRecord (record, &li->inp, readData, writeData);
|
||||
return streamInitRecord (record, &li->inp, readData, writeData) == ERROR ?
|
||||
ERROR : OK;
|
||||
}
|
||||
|
||||
struct {
|
||||
|
@ -29,7 +29,10 @@ static long readData (dbCommon *record, format_t *format)
|
||||
|
||||
if (format->type == DBF_LONG || format->type == DBF_ENUM)
|
||||
{
|
||||
return streamScanf (record, format, &lo->val);
|
||||
long val;
|
||||
if (streamScanf (record, format, &val)) return ERROR;
|
||||
lo->val = val;
|
||||
return OK;
|
||||
}
|
||||
return ERROR;
|
||||
}
|
||||
@ -40,7 +43,7 @@ static long writeData (dbCommon *record, format_t *format)
|
||||
|
||||
if (format->type == DBF_LONG || format->type == DBF_ENUM)
|
||||
{
|
||||
return streamPrintf (record, format, lo->val);
|
||||
return streamPrintf (record, format, (long) lo->val);
|
||||
}
|
||||
return ERROR;
|
||||
}
|
||||
@ -49,7 +52,8 @@ static long initRecord (dbCommon *record)
|
||||
{
|
||||
longoutRecord *lo = (longoutRecord *) record;
|
||||
|
||||
return streamInitRecord (record, &lo->out, readData, writeData);
|
||||
return streamInitRecord (record, &lo->out, readData, writeData) == ERROR ?
|
||||
ERROR : OK;
|
||||
}
|
||||
|
||||
struct {
|
||||
|
@ -66,7 +66,8 @@ static long initRecord (dbCommon *record)
|
||||
mbbiDirectRecord *mbbiD = (mbbiDirectRecord *) record;
|
||||
|
||||
mbbiD->mask <<= mbbiD->shft;
|
||||
return streamInitRecord (record, &mbbiD->inp, readData, writeData);
|
||||
return streamInitRecord (record, &mbbiD->inp, readData, writeData) == ERROR ?
|
||||
ERROR : OK;
|
||||
}
|
||||
|
||||
struct {
|
||||
|
@ -114,7 +114,8 @@ static long initRecord (dbCommon *record)
|
||||
mbbiRecord *mbbi = (mbbiRecord *) record;
|
||||
|
||||
mbbi->mask <<= mbbi->shft;
|
||||
return streamInitRecord (record, &mbbi->inp, readData, writeData);
|
||||
return streamInitRecord (record, &mbbi->inp, readData, writeData) == ERROR ?
|
||||
ERROR : OK;
|
||||
}
|
||||
|
||||
struct {
|
||||
|
@ -48,7 +48,8 @@ static long initRecord (dbCommon *record)
|
||||
{
|
||||
stringinRecord *si = (stringinRecord *) record;
|
||||
|
||||
return streamInitRecord (record, &si->inp, readData, writeData);
|
||||
return streamInitRecord (record, &si->inp, readData, writeData) == ERROR ?
|
||||
ERROR : OK;
|
||||
}
|
||||
|
||||
struct {
|
||||
|
@ -48,7 +48,8 @@ static long initRecord (dbCommon *record)
|
||||
{
|
||||
stringoutRecord *so = (stringoutRecord *) record;
|
||||
|
||||
return streamInitRecord (record, &so->out, readData, writeData);
|
||||
return streamInitRecord (record, &so->out, readData, writeData) == ERROR ?
|
||||
ERROR : OK;
|
||||
}
|
||||
|
||||
struct {
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "devStream.h"
|
||||
#include <waveformRecord.h>
|
||||
#include <string.h>
|
||||
#include <errlog.h>
|
||||
#include <epicsExport.h>
|
||||
|
||||
static long readData (dbCommon *record, format_t *format)
|
||||
@ -43,10 +44,10 @@ static long readData (dbCommon *record, format_t *format)
|
||||
switch (wf->ftvl)
|
||||
{
|
||||
case DBF_DOUBLE:
|
||||
((double *)wf->bptr)[wf->nord] = dval;
|
||||
((epicsFloat64 *)wf->bptr)[wf->nord] = (epicsFloat64)dval;
|
||||
break;
|
||||
case DBF_FLOAT:
|
||||
((float *)wf->bptr)[wf->nord] = (float)dval;
|
||||
((epicsFloat32 *)wf->bptr)[wf->nord] = (epicsFloat32)dval;
|
||||
break;
|
||||
default:
|
||||
errlogSevPrintf (errlogFatal,
|
||||
@ -66,23 +67,23 @@ static long readData (dbCommon *record, format_t *format)
|
||||
switch (wf->ftvl)
|
||||
{
|
||||
case DBF_DOUBLE:
|
||||
((double *)wf->bptr)[wf->nord] = (double)lval;
|
||||
((epicsFloat64 *)wf->bptr)[wf->nord] = (epicsFloat64)lval;
|
||||
break;
|
||||
case DBF_FLOAT:
|
||||
((float *)wf->bptr)[wf->nord] = (float)lval;
|
||||
((epicsFloat32 *)wf->bptr)[wf->nord] = (epicsFloat32)lval;
|
||||
break;
|
||||
case DBF_LONG:
|
||||
case DBF_ULONG:
|
||||
((long *)wf->bptr)[wf->nord] = lval;
|
||||
((epicsInt32 *)wf->bptr)[wf->nord] = (epicsInt32)lval;
|
||||
break;
|
||||
case DBF_SHORT:
|
||||
case DBF_USHORT:
|
||||
case DBF_ENUM:
|
||||
((short *)wf->bptr)[wf->nord] = (short)lval;
|
||||
((epicsInt16 *)wf->bptr)[wf->nord] = (epicsInt16)lval;
|
||||
break;
|
||||
case DBF_CHAR:
|
||||
case DBF_UCHAR:
|
||||
((char *)wf->bptr)[wf->nord] = (char)lval;
|
||||
((epicsInt8 *)wf->bptr)[wf->nord] = (epicsInt8)lval;
|
||||
break;
|
||||
default:
|
||||
errlogSevPrintf (errlogFatal,
|
||||
@ -156,29 +157,29 @@ static long writeData (dbCommon *record, format_t *format)
|
||||
switch (wf->ftvl)
|
||||
{
|
||||
case DBF_DOUBLE:
|
||||
dval = ((double *)wf->bptr)[nowd];
|
||||
dval = ((epicsFloat64 *)wf->bptr)[nowd];
|
||||
break;
|
||||
case DBF_FLOAT:
|
||||
dval = ((float *)wf->bptr)[nowd];
|
||||
dval = ((epicsFloat32 *)wf->bptr)[nowd];
|
||||
break;
|
||||
case DBF_LONG:
|
||||
dval = ((long *)wf->bptr)[nowd];
|
||||
dval = ((epicsInt32 *)wf->bptr)[nowd];
|
||||
break;
|
||||
case DBF_ULONG:
|
||||
dval = ((unsigned long *)wf->bptr)[nowd];
|
||||
dval = ((epicsUInt32 *)wf->bptr)[nowd];
|
||||
break;
|
||||
case DBF_SHORT:
|
||||
dval = ((short *)wf->bptr)[nowd];
|
||||
dval = ((epicsInt16 *)wf->bptr)[nowd];
|
||||
break;
|
||||
case DBF_USHORT:
|
||||
case DBF_ENUM:
|
||||
dval = ((unsigned short *)wf->bptr)[nowd];
|
||||
dval = ((epicsUInt16 *)wf->bptr)[nowd];
|
||||
break;
|
||||
case DBF_CHAR:
|
||||
dval = ((char *)wf->bptr)[nowd];
|
||||
dval = ((epicsInt8 *)wf->bptr)[nowd];
|
||||
break;
|
||||
case DBF_UCHAR:
|
||||
dval = ((unsigned char *)wf->bptr)[nowd];
|
||||
dval = ((epicsUInt8 *)wf->bptr)[nowd];
|
||||
break;
|
||||
default:
|
||||
errlogSevPrintf (errlogFatal,
|
||||
@ -196,21 +197,23 @@ static long writeData (dbCommon *record, format_t *format)
|
||||
switch (wf->ftvl)
|
||||
{
|
||||
case DBF_LONG:
|
||||
lval = ((epicsInt32 *)wf->bptr)[nowd];
|
||||
break;
|
||||
case DBF_ULONG:
|
||||
lval = ((long *)wf->bptr)[nowd];
|
||||
lval = ((epicsUInt32 *)wf->bptr)[nowd];
|
||||
break;
|
||||
case DBF_SHORT:
|
||||
lval = ((short *)wf->bptr)[nowd];
|
||||
lval = ((epicsInt16 *)wf->bptr)[nowd];
|
||||
break;
|
||||
case DBF_USHORT:
|
||||
case DBF_ENUM:
|
||||
lval = ((unsigned short *)wf->bptr)[nowd];
|
||||
lval = ((epicsUInt16 *)wf->bptr)[nowd];
|
||||
break;
|
||||
case DBF_CHAR:
|
||||
lval = ((char *)wf->bptr)[nowd];
|
||||
lval = ((epicsInt8 *)wf->bptr)[nowd];
|
||||
break;
|
||||
case DBF_UCHAR:
|
||||
lval = ((unsigned char *)wf->bptr)[nowd];
|
||||
lval = ((epicsUInt8 *)wf->bptr)[nowd];
|
||||
break;
|
||||
default:
|
||||
errlogSevPrintf (errlogFatal,
|
||||
@ -271,7 +274,8 @@ static long initRecord (dbCommon *record)
|
||||
{
|
||||
waveformRecord *wf = (waveformRecord *) record;
|
||||
|
||||
return streamInitRecord (record, &wf->inp, readData, writeData);
|
||||
return streamInitRecord (record, &wf->inp, readData, writeData) == ERROR ?
|
||||
ERROR : OK;
|
||||
}
|
||||
|
||||
struct {
|
||||
|
10
src/makedbd.pl
Normal file
@ -0,0 +1,10 @@
|
||||
if (@ARGV[0] == "-3.13") {
|
||||
shift;
|
||||
} else {
|
||||
print "variable(streamDebug, int)\n";
|
||||
print "registrar(streamRegistrar)\n";
|
||||
}
|
||||
print "driver(stream)\n";
|
||||
for (@ARGV) {
|
||||
print "device($_,INST_IO,dev${_}Stream,\"stream\")\n";
|
||||
}
|
6
src/makeref.pl
Normal file
@ -0,0 +1,6 @@
|
||||
$t=@ARGV[0];
|
||||
shift;
|
||||
for (@ARGV) {
|
||||
print "extern void* ref_${_}$t;\n";
|
||||
print "void* p$_ = ref_${_}$t;\n";
|
||||
}
|
23
srcSynApps/Makefile
Normal file
@ -0,0 +1,23 @@
|
||||
TOP=../..
|
||||
|
||||
include $(TOP)/configure/CONFIG
|
||||
|
||||
-include ../src/CONFIG_STREAM
|
||||
-include ../../src/CONFIG_STREAM
|
||||
|
||||
LIBRARY_DEFAULT = streamSynApps
|
||||
|
||||
DBD += $(LIBRARY_DEFAULT).dbd
|
||||
|
||||
ifeq ($(LOADABLE_MODULE),YES)
|
||||
SRCS += $(LIBRARY_DEFAULT)_registerRecordDeviceDriver.cpp
|
||||
endif
|
||||
SRCS += $(SYNAPPS_RECORDS:%=dev%Stream.c)
|
||||
|
||||
LIB_LIBS += stream $(EPICS_BASE_IOC_LIBS)
|
||||
|
||||
include $(TOP)/configure/RULES
|
||||
|
||||
# create streamSynApps.dbd from all SYNAPPS_RECORDS
|
||||
$(COMMON_DIR)/$(LIBRARY_DEFAULT).dbd: ../../src/CONFIG_STREAM
|
||||
$(PERL) ../../src/makedbd.pl $(SYNAPPS_RECORDS) > $@
|
108
srcSynApps/devscalcoutStream.c
Normal file
@ -0,0 +1,108 @@
|
||||
/***************************************************************
|
||||
* Stream Device record interface for scalcout records *
|
||||
* *
|
||||
* (C) 2006 Dirk Zimoch (dirk.zimoch@psi.ch) *
|
||||
* *
|
||||
* This is an EPICS record Interface for StreamDevice. *
|
||||
* Please refer to the HTML files in ../doc/ for a detailed *
|
||||
* documentation. *
|
||||
* *
|
||||
* If you do any changes in this file, you are not allowed to *
|
||||
* redistribute it any more. If there is a bug or a missing *
|
||||
* feature, send me an email and/or your patch. If I accept *
|
||||
* your changes, they will go to the next release. *
|
||||
* *
|
||||
* DISCLAIMER: If this software breaks something or harms *
|
||||
* someone, it's your problem. *
|
||||
* *
|
||||
***************************************************************/
|
||||
|
||||
#include <devStream.h>
|
||||
#include <sCalcoutRecord.h>
|
||||
#include <epicsExport.h>
|
||||
|
||||
/* scalcout record has a bug: it never calls init_record
|
||||
of the device support.
|
||||
Fix: sCalcoutRecord.c, end of init_record() add
|
||||
|
||||
if(pscalcoutDSET->init_record ) {
|
||||
return (*pscalcoutDSET->init_record)(pcalc);
|
||||
}
|
||||
The bug has been fixed in version 2-6-1.
|
||||
*/
|
||||
|
||||
static long readData (dbCommon *record, format_t *format)
|
||||
{
|
||||
scalcoutRecord *sco = (scalcoutRecord *) record;
|
||||
|
||||
switch (format->type)
|
||||
{
|
||||
case DBF_DOUBLE:
|
||||
{
|
||||
return streamScanf (record, format, &sco->val);
|
||||
}
|
||||
case DBF_LONG:
|
||||
case DBF_ENUM:
|
||||
{
|
||||
long lval;
|
||||
|
||||
if (streamScanf (record, format, &lval)) return ERROR;
|
||||
sco->val = lval;
|
||||
return OK;
|
||||
}
|
||||
case DBF_STRING:
|
||||
{
|
||||
return (streamScanfN (record, format,
|
||||
sco->sval, sizeof(sco->val)));
|
||||
}
|
||||
}
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
static long writeData (dbCommon *record, format_t *format)
|
||||
{
|
||||
scalcoutRecord *sco = (scalcoutRecord *) record;
|
||||
|
||||
switch (format->type)
|
||||
{
|
||||
case DBF_DOUBLE:
|
||||
{
|
||||
return streamPrintf (record, format, sco->oval);
|
||||
}
|
||||
case DBF_LONG:
|
||||
case DBF_ENUM:
|
||||
{
|
||||
return streamPrintf (record, format, (long)sco->oval);
|
||||
}
|
||||
case DBF_STRING:
|
||||
{
|
||||
return streamPrintf (record, format, sco->osv);
|
||||
}
|
||||
}
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
static long initRecord (dbCommon *record)
|
||||
{
|
||||
scalcoutRecord *sco = (scalcoutRecord *) record;
|
||||
|
||||
return streamInitRecord (record, &sco->out, readData, writeData);
|
||||
}
|
||||
|
||||
struct {
|
||||
long number;
|
||||
DEVSUPFUN report;
|
||||
DEVSUPFUN init;
|
||||
DEVSUPFUN init_record;
|
||||
DEVSUPFUN get_ioint_info;
|
||||
DEVSUPFUN write;
|
||||
} devscalcoutStream = {
|
||||
5,
|
||||
streamReport,
|
||||
streamInit,
|
||||
initRecord,
|
||||
streamGetIointInfo,
|
||||
streamWrite,
|
||||
};
|
||||
|
||||
epicsExportAddress(dset,devscalcoutStream);
|
4
streamApp/.cvsignore
Normal file
@ -0,0 +1,4 @@
|
||||
StreamDebug.log
|
||||
streamApp
|
||||
streamApp.exe*
|
||||
TS.*
|
78
streamApp/Makefile
Normal file
@ -0,0 +1,78 @@
|
||||
TOP=../..
|
||||
|
||||
# Look if we have EPICS R3.13 or R3.14
|
||||
ifeq ($(wildcard $(TOP)/configure),)
|
||||
# EPICS R3.13
|
||||
include $(TOP)/config/CONFIG_APP
|
||||
# The real work is in Makefile.Vx
|
||||
include $(TOP)/config/RULES_ARCHS
|
||||
else
|
||||
|
||||
# EPICS R3.14
|
||||
include $(TOP)/configure/CONFIG
|
||||
|
||||
#HOST_OPT = NO
|
||||
|
||||
PROD = streamApp
|
||||
DBD = streamApp.dbd
|
||||
|
||||
streamApp_DBD += base.dbd
|
||||
|
||||
# In 3.14.12 aaiRecord.dbd and aaoRecord.dbd are part of base.dbd
|
||||
# In earlier versions, these records are buggy and not included by default
|
||||
#streamApp_DBD += aaiRecord.dbd aaoRecord.dbd
|
||||
|
||||
PROD_SRCS += streamApp_registerRecordDeviceDriver.cpp
|
||||
PROD_SRCS_DEFAULT = streamAppMain.cc
|
||||
PROD_SRCS_vxWorks = -nil-
|
||||
|
||||
PROD_LIBS = stream
|
||||
|
||||
ifdef ASYN
|
||||
# edit asynRegistrars.dbd if necessary
|
||||
streamApp_DBD += asynRegistrars.dbd
|
||||
# add asyn.dbd if you want to have asyn Record and asyn device supports
|
||||
# streamApp_DBD += asyn.dbd
|
||||
PROD_LIBS += asyn
|
||||
endif
|
||||
|
||||
ifneq ($(words $(CALC) $(SYNAPPS)), 0)
|
||||
# With synApps scalcout record
|
||||
streamApp_DBD += calcSupport.dbd
|
||||
streamApp_DBD += streamSynApps.dbd
|
||||
PROD_LIBS += calc
|
||||
# older calc versions require sscan
|
||||
#PROD_LIBS += sscan
|
||||
PROD_LIBS_DEFAULT += streamSynApps
|
||||
endif
|
||||
|
||||
streamApp_DBD += stream.dbd
|
||||
|
||||
ifdef PCRE
|
||||
LIB_LIBS += pcre
|
||||
else
|
||||
ifneq ($(words $(PCRE_LIB) $(PCRE_INCLUDE)),0)
|
||||
PROD_SYS_LIBS_DEFAULT += pcre
|
||||
PROD_SYS_LIBS_WIN32 += $(PCRE_LIB)\\pcre
|
||||
SHRLIB_DEPLIB_DIRS += $(PCRE_LIB)
|
||||
endif
|
||||
endif
|
||||
|
||||
PROD_LIBS += $(EPICS_BASE_IOC_LIBS)
|
||||
|
||||
# Write StreamDevice debug output to this file
|
||||
# If unset output goes to stdout
|
||||
CPPFLAGS += -DDEBUGFILE=StreamDebug.log
|
||||
|
||||
include $(TOP)/configure/RULES
|
||||
|
||||
ifeq ($(EPICS_REVISION),14)
|
||||
clean:: myclean
|
||||
else
|
||||
clean: myclean
|
||||
endif
|
||||
|
||||
myclean:
|
||||
$(RM) core* StreamDebug.log
|
||||
|
||||
endif
|
8
streamApp/Makefile.Host
Normal file
@ -0,0 +1,8 @@
|
||||
TOP = ../../..
|
||||
|
||||
include $(TOP)/config/CONFIG_APP
|
||||
|
||||
DBDEXPAND = streamAppInclude-3-13.dbd
|
||||
DBDNAME = streamApp.dbd
|
||||
|
||||
include $(TOP)/config/RULES.Host
|
20
streamApp/Makefile.Vx
Normal file
@ -0,0 +1,20 @@
|
||||
TOP = ../../..
|
||||
|
||||
include $(TOP)/config/CONFIG_APP
|
||||
|
||||
LIBNAME = streamApp
|
||||
|
||||
LDLIBS += $(COMPAT_BIN)/compatLib
|
||||
LDLIBS += $(ASYN_BIN)/asynLib
|
||||
LDLIBS += $(INSTALL_BIN)/streamLib
|
||||
|
||||
include ../base-3-13LIBOBJS
|
||||
|
||||
# Write StreamDevice debug output to this file
|
||||
CPPFLAGS += -DDEBUGFILE=StreamDebug.log
|
||||
|
||||
include $(TOP)/config/RULES.Vx
|
||||
include $(TOP)/config/RULES.munch
|
||||
|
||||
# Rebuild when LIBOBJS change
|
||||
$(LIBNAME): ../base-3-13LIBOBJS
|
9
streamApp/asynRegistrars.dbd
Normal file
@ -0,0 +1,9 @@
|
||||
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)
|
242
streamApp/base-3-13.dbd
Normal file
@ -0,0 +1,242 @@
|
||||
include "menuGlobal.dbd"
|
||||
include "menuConvert.dbd"
|
||||
include "aiRecord.dbd"
|
||||
include "aaiRecord.dbd"
|
||||
include "aoRecord.dbd"
|
||||
include "aaoRecord.dbd"
|
||||
include "biRecord.dbd"
|
||||
include "boRecord.dbd"
|
||||
include "calcRecord.dbd"
|
||||
include "calcoutRecord.dbd"
|
||||
include "compressRecord.dbd"
|
||||
include "dfanoutRecord.dbd"
|
||||
#include "egRecord.dbd"
|
||||
#include "egeventRecord.dbd"
|
||||
#include "erRecord.dbd"
|
||||
#include "ereventRecord.dbd"
|
||||
include "eventRecord.dbd"
|
||||
include "fanoutRecord.dbd"
|
||||
#include "histogramRecord.dbd"
|
||||
include "longinRecord.dbd"
|
||||
include "longoutRecord.dbd"
|
||||
include "mbbiRecord.dbd"
|
||||
include "mbbiDirectRecord.dbd"
|
||||
include "mbboRecord.dbd"
|
||||
include "mbboDirectRecord.dbd"
|
||||
include "permissiveRecord.dbd"
|
||||
#include "pidRecord.dbd"
|
||||
#include "pulseCounterRecord.dbd"
|
||||
#include "pulseDelayRecord.dbd"
|
||||
#include "pulseTrainRecord.dbd"
|
||||
#include "scanRecord.dbd"
|
||||
include "selRecord.dbd"
|
||||
include "seqRecord.dbd"
|
||||
include "stateRecord.dbd"
|
||||
#include "steppermotorRecord.dbd"
|
||||
include "stringinRecord.dbd"
|
||||
include "stringoutRecord.dbd"
|
||||
include "subRecord.dbd"
|
||||
#include "gsubRecord.dbd"
|
||||
#include "palRecord.dbd"
|
||||
include "subArrayRecord.dbd"
|
||||
#include "timerRecord.dbd"
|
||||
#include "waitRecord.dbd"
|
||||
include "waveformRecord.dbd"
|
||||
device(ai,CONSTANT,devAiSoft,"Soft Channel")
|
||||
device(ai,CONSTANT,devAiSoftRaw,"Raw Soft Channel")
|
||||
#device(ai,VME_IO,devAiXy566Se,"XYCOM-566 SE Scanned")
|
||||
#device(ai,VME_IO,devAiXy566Di,"XYCOM-566 Dif Scanned")
|
||||
#device(ai,VME_IO,devAiXy566DiL,"XYCOM-566 Dif Latched")
|
||||
#device(ai,VME_IO,devAiDvx2502,"DVX-2502")
|
||||
#device(ai,CONSTANT,devAiTestAsyn,"Test Asyn")
|
||||
#device(ai,AB_IO,devAiAb1771Il,"AB-1771IL-Analog In")
|
||||
#device(ai,AB_IO,devAiAb1771Ife,"AB-1771IFE")
|
||||
#device(ai,AB_IO,devAiAb1771Ixe,"AB-1771IXE-Millivolt In")
|
||||
#device(ai,AB_IO,devAiAb1771IfeSe,"AB-1771IFE-SE")
|
||||
#device(ai,AB_IO,devAiAb1771IfeMa,"AB-1771IFE-4to20MA")
|
||||
#device(ai,AB_IO,devAiAb1771Ife0to5V,"AB-1771IFE-0to5Volt")
|
||||
#device(ai,AB_IO,devAiAb1771IrPlatinum,"AB-1771RTD-Platinum")
|
||||
#device(ai,AB_IO,devAiAb1771IrCopper,"AB-1771RTD-Copper")
|
||||
#device(ai,CAMAC_IO,devAiCamac,"Camac")
|
||||
#device(ai,VME_IO,devAiAt5Vxi,"VXI-AT5-AI")
|
||||
#device(ai,GPIB_IO,devAiK486Gpib,"Keithley-486")
|
||||
#device(ai,VME_IO,devAiKscV215,"KSC-V215")
|
||||
#device(ai,INST_IO,devAiVXStats,"VX stats")
|
||||
#device(aai,CAMAC_IO,devAaiCamac,"Camac")
|
||||
device(ao,CONSTANT,devAoSoft,"Soft Channel")
|
||||
device(ao,CONSTANT,devAoSoftRaw,"Raw Soft Channel")
|
||||
#device(ao,VME_IO,devAoVmiVme4100,"VMIVME-4100")
|
||||
#device(ao,CONSTANT,devAoTestAsyn,"Test Asyn")
|
||||
#device(ao,AB_IO,devAoAb1771Ofe,"AB-1771OFE")
|
||||
#device(ao,CAMAC_IO,devAoCamac,"Camac")
|
||||
#device(ao,VME_IO,devAoAt5Vxi,"VXI-AT5-AO")
|
||||
#device(ao,INST_IO,devAoVXStats,"VX stats")
|
||||
device(bi,CONSTANT,devBiSoft,"Soft Channel")
|
||||
device(bi,CONSTANT,devBiSoftRaw,"Raw Soft Channel")
|
||||
#device(bi,VME_IO,devBiMpv910,"MPV-910")
|
||||
#device(bi,VME_IO,devBiXVme210,"XVME-210")
|
||||
#device(bi,CONSTANT,devBiTestAsyn,"Test Asyn")
|
||||
#device(bi,AB_IO,devBiAb,"AB-Binary Input")
|
||||
#device(bi,AB_IO,devBiAb16,"AB-16 bit BI")
|
||||
#device(bi,AB_IO,devBiAb32,"AB-32 bit BI")
|
||||
#device(bi,CAMAC_IO,devBiCamac,"Camac")
|
||||
#device(bi,VME_IO,devBiAt5Vxi,"VXI-AT5-BI")
|
||||
#device(bi,VME_IO,devBiXy240,"XYCOM-240")
|
||||
#device(bi,VME_IO,devBiHpe1368a,"VXI-HPE1368-VS")
|
||||
#device(bi,VME_IO,devBiAt8Fp,"AT8-FP10S")
|
||||
#device(bi,VME_IO,devBiAvme9440,"AVME9440 I")
|
||||
device(bo,CONSTANT,devBoSoft,"Soft Channel")
|
||||
device(bo,CONSTANT,devBoSoftRaw,"Raw Soft Channel")
|
||||
#device(bo,VME_IO,devBoMpv902,"MPV-902")
|
||||
#device(bo,VME_IO,devBoXVme220,"XVME-220")
|
||||
#device(bo,CONSTANT,devBoTestAsyn,"Test Asyn")
|
||||
#device(bo,AB_IO,devBoAb,"AB-Binary Output")
|
||||
#device(bo,AB_IO,devBoAb16,"AB-16 bit BO")
|
||||
#device(bo,AB_IO,devBoAb32,"AB-32 bit BO")
|
||||
#device(bo,CAMAC_IO,devBoCamac,"Camac")
|
||||
#device(bo,VME_IO,devBoAt5Vxi,"VXI-AT5-BO")
|
||||
#device(bo,GPIB_IO,devBoK486Gpib,"Keithley-486")
|
||||
#device(bo,VME_IO,devBoXy240,"XYCOM-240")
|
||||
#device(bo,VME_IO,devBoHpe1368a,"VXI-HPE1368-VS")
|
||||
#device(bo,VME_IO,devBoAt8Fp,"AT8-FP10S")
|
||||
#device(bo,VME_IO,devBoAvme9440,"AVME9440 O")
|
||||
device(event,CONSTANT,devEventSoft,"Soft Channel")
|
||||
#device(event,VME_IO,devEventMz8310,"Mizar-8310")
|
||||
#device(event,CONSTANT,devEventTestIoEvent,"Test IoEvent")
|
||||
#device(event,VME_IO,devErEpicsEvent,"APS event receiver")
|
||||
#device(histogram,CONSTANT,devHistogramSoft,"Soft Channel")
|
||||
#device(histogram,CONSTANT,devHistogramTestAsyn,"Test Asyn")
|
||||
device(longin,CONSTANT,devLiSoft,"Soft Channel")
|
||||
#device(longin,CAMAC_IO,devLiCamac,"Camac")
|
||||
#device(longin,INST_IO,devLiVXStats,"VX stats")
|
||||
device(longout,CONSTANT,devLoSoft,"Soft Channel")
|
||||
#device(longout,CAMAC_IO,devLoCamac,"Camac")
|
||||
device(mbbi,CONSTANT,devMbbiSoft,"Soft Channel")
|
||||
device(mbbi,CONSTANT,devMbbiSoftRaw,"Raw Soft Channel")
|
||||
#device(mbbi,VME_IO,devMbbiMpv910,"MPV-910")
|
||||
#device(mbbi,VME_IO,devMbbiXVme210,"XVME-210")
|
||||
#device(mbbi,CONSTANT,devMbbiTestAsyn,"Test Asyn")
|
||||
#device(mbbi,AB_IO,devMbbiAb,"AB-Binary Input")
|
||||
#device(mbbi,AB_IO,devMbbiAb16,"AB-16 bit BI")
|
||||
#device(mbbi,AB_IO,devMbbiAb32,"AB-32 bit BI")
|
||||
#device(mbbi,AB_IO,devMbbiAbAdapterStat,"AB-Adapter Status")
|
||||
#device(mbbi,AB_IO,devMbbiAbCardStat,"AB-Card Status")
|
||||
#device(mbbi,CAMAC_IO,devMbbiCamac,"Camac")
|
||||
#device(mbbi,VME_IO,devMbbiAt5Vxi,"VXI-AT5-BI")
|
||||
#device(mbbi,VME_IO,devMbbiXy240,"XYCOM-240")
|
||||
#device(mbbi,VME_IO,devMbbiHpe1368a,"VXI-HPE1368-VS")
|
||||
#device(mbbi,VME_IO,devMbbiAt8Fp,"AT8-FP10S")
|
||||
#device(mbbi,VME_IO,devMbbiAvme9440,"AVME9440 I")
|
||||
device(mbbiDirect,CONSTANT,devMbbiDirectSoft,"Soft Channel")
|
||||
device(mbbiDirect,CONSTANT,devMbbiDirectSoftRaw,"Raw Soft Channel")
|
||||
#device(mbbiDirect,VME_IO,devMbbiDirectMpv910,"MPV-910")
|
||||
#device(mbbiDirect,VME_IO,devMbbiDirectXVme210,"XVME-210")
|
||||
#device(mbbiDirect,AB_IO,devMbbiDirectAb,"AB-Binary Input")
|
||||
#device(mbbiDirect,AB_IO,devMbbiDirectAb16,"AB-16 bit BI")
|
||||
#device(mbbiDirect,AB_IO,devMbbiDirectAb32,"AB-32 bit BI")
|
||||
#device(mbbiDirect,CAMAC_IO,devMbbiDirectCamac,"Camac")
|
||||
#device(mbbiDirect,VME_IO,devMbbiDirectAt5Vxi,"VXI-AT5-BI")
|
||||
device(mbbo,CONSTANT,devMbboSoft,"Soft Channel")
|
||||
device(mbbo,CONSTANT,devMbboSoftRaw,"Raw Soft Channel")
|
||||
#device(mbbo,VME_IO,devMbboMpv902,"MPV-902")
|
||||
#device(mbbo,VME_IO,devMbboXVme220,"XVME-220")
|
||||
#device(mbbo,CONSTANT,devMbboTestAsyn,"Test Asyn")
|
||||
#device(mbbo,AB_IO,devMbboAb,"AB-Binary Output")
|
||||
#device(mbbo,AB_IO,devMbboAb16,"AB-16 bit BO")
|
||||
#device(mbbo,AB_IO,devMbboAb32,"AB-32 bit BO")
|
||||
#device(mbbo,VME_IO,devMbboAt5Vxi,"VXI-AT5-BO")
|
||||
#device(mbbo,GPIB_IO,devMbboK486Gpib,"Keithley-486")
|
||||
#device(mbbo,VME_IO,devMbboXy240,"XYCOM-240")
|
||||
#device(mbbo,VME_IO,devMbboHpe1368a,"VXI-HPE1368-VS")
|
||||
#device(mbbo,VME_IO,devMbboAt8Fp,"AT8-FP10S")
|
||||
#device(mbbo,VME_IO,devMbboAvme9440,"AVME9440 O")
|
||||
device(mbboDirect,CONSTANT,devMbboDirectSoft,"Soft Channel")
|
||||
device(mbboDirect,CONSTANT,devMbboDirectSoftRaw,"Raw Soft Channel")
|
||||
#device(mbboDirect,VME_IO,devMbboDirectMpv902,"MPV-902")
|
||||
#device(mbboDirect,VME_IO,devMbboDirectXVme220,"XVME-220")
|
||||
#device(mbboDirect,AB_IO,devMbboDirectAb,"AB-Binary Output")
|
||||
#device(mbboDirect,AB_IO,devMbboDirectAb16,"AB-16 bit BO")
|
||||
#device(mbboDirect,AB_IO,devMbboDirectAb32,"AB-32 bit BO")
|
||||
#device(mbboDirect,CAMAC_IO,devMbboDirectCamac,"Camac")
|
||||
#device(mbboDirect,VME_IO,devMbboDirectAt5Vxi,"VXI-AT5-BO")
|
||||
#device(pulseCounter,VME_IO,devPcMz8310,"Mizar-8310")
|
||||
#device(pulseDelay,VME_IO,devPdMz8310,"Mizar-8310")
|
||||
#device(pulseTrain,CONSTANT,devPtSoft,"Soft Channel")
|
||||
#device(pulseTrain,VME_IO,devPtMz8310,"Mizar-8310")
|
||||
#device(steppermotor,VME_IO,devSmCompumotor1830,"Compumotor 1830")
|
||||
#device(steppermotor,VME_IO,devSmOms6Axis,"OMS 6-Axis")
|
||||
#device(steppermotor,AB_IO,devSmAB1746Hstp1,"Allen Bradley 1746-HTSP1")
|
||||
device(stringin,CONSTANT,devSiSoft,"Soft Channel")
|
||||
#device(stringin,CONSTANT,devSiTestAsyn,"Test Asyn")
|
||||
device(stringout,CONSTANT,devSoSoft,"Soft Channel")
|
||||
#device(stringout,CONSTANT,devSoTestAsyn,"Test Asyn")
|
||||
device(subArray,CONSTANT,devSASoft,"Soft Channel")
|
||||
#device(timer,VME_IO,devTmMizar8310,"Mizar-8310")
|
||||
#device(timer,VME_IO,devTmDg535,"DG 535")
|
||||
#device(timer,VME_IO,devTmAt5Vxi,"VXI-AT5-TIME")
|
||||
device(waveform,CONSTANT,devWfSoft,"Soft Channel")
|
||||
#device(waveform,VME_IO,devWfXy566Sc,"XYCOM-566 Single Channel")
|
||||
#device(waveform,VME_IO,devWfComet,"Comet Digitizer")
|
||||
#device(waveform,VME_IO,devWfJoergerVtr1,"Joerger Digitizer")
|
||||
#device(waveform,CONSTANT,devWfTestAsyn,"Test Asyn")
|
||||
#device(waveform,VME_IO,devWfDvx2502,"DVX-2502")
|
||||
#device(waveform,VME_IO,devWfPentek4261,"Pentek 4261")
|
||||
#device(waveform,CAMAC_IO,devWfCamac,"Camac")
|
||||
#device(waveform,VME_IO,devWfJoergerVtr1,"Joerger-VTR1")
|
||||
#device(waveform,VME_IO,devWfComet,"Omnibyte-COMET")
|
||||
#device(eg,VME_IO,devEg,"APS event generator G")
|
||||
#device(egevent,VME_IO,devEgEvent,"APS event generator E")
|
||||
#device(er,VME_IO,devEr,"APS event receiver")
|
||||
#device(erevent,VME_IO,devErevent,"APS event receiver")
|
||||
#device(wait,CONSTANT,devWaitIoEvent,"Soft Channel")
|
||||
#device(ai,INST_IO,devAiCan,"CANbus")
|
||||
#device(ao,INST_IO,devAoCan,"CANbus")
|
||||
#device(bi,INST_IO,devBiCan,"CANbus")
|
||||
#device(bo,INST_IO,devBoCan,"CANbus")
|
||||
#device(mbbi,INST_IO,devMbbiCan,"CANbus")
|
||||
#device(mbbo,INST_IO,devMbboCan,"CANbus")
|
||||
#device(mbbiDirect,INST_IO,devMbbiDirectCan,"CANbus")
|
||||
#device(mbboDirect,INST_IO,devMbboDirectCan,"CANbus")
|
||||
#device(bi,VME_IO,devBiVmic2534,"Vmic2534")
|
||||
#device(bo,VME_IO,devBoVmic2534,"Vmic2534")
|
||||
#device(mbbi,VME_IO,devMbbiVmic2534,"Vmic2534")
|
||||
#device(mbbo,VME_IO,devMbboVmic2534,"Vmic2534")
|
||||
|
||||
#include "symb.dbd"
|
||||
|
||||
#device(ai,AB_IO,devAiAbSlcDcm,"AB-SLC500DCM")
|
||||
#device(ai,AB_IO,devAiAbSlcDcmSigned,"AB-SLC500DCM-Signed")
|
||||
#device(ao,AB_IO,devAoAbSlcDcm,"AB-SLC500DCM")
|
||||
#device(longin,AB_IO,devLiAbSlcDcm,"AB-SLC500DCM")
|
||||
#device(longout,AB_IO,devLoAbSlcDcm,"AB-SLC500DCM")
|
||||
|
||||
#driver(drvXy010)
|
||||
#driver(drvVxi)
|
||||
#driver(drvGpib)
|
||||
#driver(drvBitBus)
|
||||
#driver(drvBb910)
|
||||
#driver(drvXy210)
|
||||
#driver(drvBb902)
|
||||
#driver(drvXy220)
|
||||
#driver(drvXy566)
|
||||
#driver(drvDvx)
|
||||
#driver(drvVmi4100)
|
||||
#driver(drvAb)
|
||||
#driver(drvAt5Vxi)
|
||||
#driver(drvCompuSm)
|
||||
#driver(drvOms)
|
||||
#driver(drvMz8310)
|
||||
#driver(drvHpe1368a)
|
||||
#driver(drvXy240)
|
||||
#driver(drvKscV215)
|
||||
#driver(drvComet)
|
||||
#driver(drvJgvtr1)
|
||||
#driver(drvFp)
|
||||
#driver(drvFpm)
|
||||
#driver(drvIpac)
|
||||
#driver(drvTip810)
|
||||
#driver(drvVmi2534)
|
||||
#include "bptTypeJdegC.dbd"
|
||||
#include "bptTypeJdegF.dbd"
|
||||
#include "bptTypeKdegC.dbd"
|
||||
#include "bptTypeKdegF.dbd"
|
216
streamApp/base-3-13LIBOBJS
Normal file
@ -0,0 +1,216 @@
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/aaiRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/aaoRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/aiRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/aoRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/biRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/boRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/calcRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/calcoutRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/compressRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/dfanoutRecord.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/egRecord.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/egeventRecord.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/erRecord.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/ereventRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/eventRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/fanoutRecord.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/gsubRecord.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/histogramRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/longinRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/longoutRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/mbbiRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/mbbiDirectRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/mbboRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/mbboDirectRecord.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/palRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/permissiveRecord.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/pidRecord.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/pulseCounterRecord.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/pulseDelayRecord.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/pulseTrainRecord.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/scanRecord.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/recWaitCa.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/recDynLink.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/recDynLinkTest.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/selRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/seqRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/stateRecord.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/steppermotorRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/stringinRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/stringoutRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/subRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/subArrayRecord.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/timerRecord.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/waitRecord.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/waveformRecord.o
|
||||
#
|
||||
# Device Support
|
||||
#
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAaiCamac.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAiCamac.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAiDvx2502.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAiKscV215.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devAiSoft.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devAiSoftRaw.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devVXStats.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devAiTestAsyn.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAiXy566Di.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAiXy566DiL.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAiXy566Se.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAaoCamac.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAoCamac.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devAoSoft.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devAoSoftRaw.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devAoTestAsyn.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAoVmiVme4100.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devApsEg.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devApsEr.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAt5Vxi.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAt8Fp.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAvme9440.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devBiCamac.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devBiMpv910.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devBiSoft.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devBiSoftRaw.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devBiTestAsyn.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devBiXVme210.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devBoCamac.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devBoMpv902.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devBoSoft.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devBoSoftRaw.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devBoTestAsyn.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devBoXVme220.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devCommonGpib.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devEventSoft.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devEventTestIoEvent.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devHistogramSoft.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devHistogramTestAsyn.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devHpe1368a.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devLiCamac.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devLiSoft.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devLoCamac.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devLoSoft.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devMbbiCamac.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devMbbiDirectCamac.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devMbbiDirectMpv910.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devMbbiDirectSoft.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devMbbiDirectSoftRaw.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devMbbiDirectXVme210.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devMbbiMpv910.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devMbbiSoft.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devMbbiSoftRaw.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devMbbiTestAsyn.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devMbbiXVme210.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devMbboCamac.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devMbboDirectCamac.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devMbboDirectMpv902.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devMbboDirectSoft.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devMbboDirectSoftRaw.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devMbboDirectXVme220.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devMbboMpv902.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devMbboSoft.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devMbboSoftRaw.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devMbboTestAsyn.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devMbboXVme220.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devMz8310.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devPtSoft.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devSASoft.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devSiSoft.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devSiTestAsyn.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devSmCompumotor1830.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devSmOms6Axis.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devSoSoft.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devSoTestAsyn.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devTimerMz8310.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devWfCamac.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devWfComet.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devWfDvx2502.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devWfJoergerVtr1.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devWfSoft.o
|
||||
LIBOBJS += $(EPICS_BASE_BIN)/devWfTestAsyn.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devWfXy566Sc.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devWfPentek4261.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devXy240.o
|
||||
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAB1771IFE.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAB1771IL.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAB1771IR.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAB1771IXE.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAB1771OFE.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devABSLCDCM.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devABBINARY.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devABStatus.o
|
||||
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAiCan.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAoCan.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devBiCan.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devBoCan.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devMbbiCan.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devMbboCan.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devMbbiDirectCan.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devMbboDirectCan.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devVmic2534.o
|
||||
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/symb
|
||||
|
||||
#
|
||||
# Driver support ANSI
|
||||
#
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvAb.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvAt5Vxi.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvEpvxi.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvEpvxiMsg.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvHp1404a.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvHpe1368a.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvHpe1445a.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvKscV215.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvMz8310.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvStc.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvTime.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvCaenV265.o
|
||||
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvVipc310.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvVipc610.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvIpMv162.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvIpac.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvTip810.o
|
||||
#
|
||||
# Driver support NON ANSI
|
||||
#
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/module_types.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvBB232.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvBb902.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvBb910.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvBitBus.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvComet.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvCompuSm.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvDvx.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvFp.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvFpm.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvGpib.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvJgvtr1.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvMsg.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvOms.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvTranServ.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvVmi4100.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvXy010.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvXy210.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvXy220.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvXy240.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvXy566.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/drvVmic2534.o
|
||||
#
|
||||
#from src/devOpt
|
||||
#
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devAnalytekGpib.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devBBInteract.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devGpibInteract.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devK486Gpib.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devLibOpt.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devXxDc5009Gpib.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devXxDg535Gpib.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devXxK196Gpib.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devXxK263Gpib.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devXxSkeletonGpib.o
|
||||
#LIBOBJS += $(EPICS_BASE_BIN)/devXxSr620Gpib.o
|
||||
|
21
streamApp/example-3-13.cmd
Normal file
@ -0,0 +1,21 @@
|
||||
BIN="<library directory>"
|
||||
DBD="<dbd directory>"
|
||||
HOME="<ioc home direcory>"
|
||||
|
||||
#where can protocols be located?
|
||||
STREAM_PROTOCOL_PATH=".:protocols:../protocols/"
|
||||
|
||||
cd BIN
|
||||
ld < iocCore
|
||||
ld < streamApp.munch
|
||||
dbLoadDatabase "streamApp.dbd",DBD
|
||||
|
||||
drvAsynIPPortConfigure "terminal", "xxx.xxx.xxx.xxx:40000"
|
||||
|
||||
#load the records
|
||||
cd HOME
|
||||
dbLoadRecords "example.db","PREFIX=DZ"
|
||||
|
||||
#lots of debug output
|
||||
#streamDebug=1
|
||||
iocInit
|
38
streamApp/example.cmd
Normal file
@ -0,0 +1,38 @@
|
||||
dbLoadDatabase "O.Common/streamApp.dbd"
|
||||
streamApp_registerRecordDeviceDriver
|
||||
|
||||
#where can protocols be located?
|
||||
epicsEnvSet "STREAM_PROTOCOL_PATH", ".:protocols:../protocols/"
|
||||
|
||||
#setup the busses
|
||||
|
||||
#example serial port setup
|
||||
#drvAsynSerialPortConfigure "COM2", "/dev/ttyS1"
|
||||
#asynOctetSetInputEos "COM2",0,"\r\n"
|
||||
#asynOctetSetOutputEos "COM2",0,"\r\n"
|
||||
#asynSetOption ("COM2", 0, "baud", "9600")
|
||||
#asynSetOption ("COM2", 0, "bits", "8")
|
||||
#asynSetOption ("COM2", 0, "parity", "none")
|
||||
#asynSetOption ("COM2", 0, "stop", "1")
|
||||
#asynSetOption ("COM2", 0, "clocal", "Y")
|
||||
#asynSetOption ("COM2", 0, "crtscts", "N")
|
||||
|
||||
#example telnet style IP port setup
|
||||
drvAsynIPPortConfigure "terminal", "localhost:40000"
|
||||
asynOctetSetInputEos "terminal",0,"\r\n"
|
||||
asynOctetSetOutputEos "terminal",0,"\r\n"
|
||||
|
||||
#example VXI11 (GPIB via IP) port setup
|
||||
#vxi11Configure "GPIB","ins023",1,5.0,"hpib"
|
||||
|
||||
#load the records
|
||||
dbLoadRecords "example.db","PREFIX=DZ"
|
||||
#dbLoadRecords "scalcout.db","PREFIX=DZ"
|
||||
|
||||
#lots! of debug output
|
||||
#var streamDebug 1
|
||||
|
||||
iocInit
|
||||
|
||||
#enable debug output
|
||||
var streamDebug 1
|