+
+
+StreamDevice: Record Processing
+
+
+
+
+
+
+StreamDevice: Record Processing
+
+
+1. Normal Processing
+
+StreamDevice is an asynchronous device support
+(see IOC Application Developer's Guide chapter 12:
+Device Support).
+Whenever the record is processed, the protocol
+is scheduled to start and the record is left active (PACT=1
).
+The protocol itself runs in another thread.
+That means that any waiting in the protocol does not delay any other
+part of the IOC.
+
+
+After the protocol has finished, the record is processed again, leaving
+PACT=0
this time, triggering monitors and processing
+the forward link FLNK
.
+Note that input links with PP flag pointing to a StreamDevice
+record will read the old value first and start the protocol
+afterward.
+This is a problem all asynchronous EPICS device supports have.
+
+
+The first out
command in the protocol locks the device
+for exclusive access.
+That means that no other record can communicate with that device.
+This ensures that replies given by the device reach the record
+which has sent the request.
+On a bus with many devices on different addresses, this
+normally locks only one device.
+The device is unlocked when the protocol terminates.
+Another record trying to lock the same device has to wait and
+might get a LockTimeout
.
+
+
+If any error happens, the protocol is aborted. The record will have
+its SEVR
field set to INVALID
and its
+STAT
field to something describing the error:
+
+
+ TIMEOUT
+
+ The device could not be locked (LockTimeout
) or the
+ device did not reply (ReplyTimeout
).
+
+ WRITE
+
+ Output could not be written to the device (WriteTimeout
).
+
+ READ
+
+ Input from the device started but stopped unexpectedly
+ (ReadTimeout
).
+
+ COMM
+
+ The device driver reported some other communication error (e.g.
+ unplugged cable).
+
+ CALC
+
+ Input did not match the argument string of the in
command
+ or it contained values the record did not accept.
+
+ UDF
+
+ Some fatal error happened or the record has not been initialized
+ correctly (e.g. because the protocol is erroneous).
+
+
+
+
+If the protocol is aborted, an
+exception handler might be executed
+if defined.
+Even if the exception handler can complete with no further error, the
+protocol will not resume and SEVR
and STAT
+will be set according to the original error.
+
+
+
+2. Initialization
+
+Often, it is required to initialize records from the hardware after
+booting the IOC, especially output records.
+For this purpose, initialization is formally handled as an
+exception .
+The @init
handler is called as part of the
+initRecord()
function during iocInit
+before any scan task starts.
+
+
+In contrast to normal processing , the protocol
+is handled synchronously.
+That means that initRecord()
does not return before the
+@init
handler has finished.
+Thus, the records initialize one after the other.
+The scan tasks are not started and iocInit
does not
+return before all @init
handlers have finished.
+If the handler fails, the record remains uninitialized:
+UDF=1
, SEVR=INVALID
,
+STAT=UDF
.
+
+
+The @init
handler has nothing to do with the
+PINI
field.
+The handler does not process the record nor does it trigger
+forward links or other PP links.
+It runs before PINI
is handled.
+If the record has PINI=YES
, the PINI
+processing is a normal processing after the
+@init
handlers of all records have completed.
+
+
+Depending on the record type, format converters might work
+slightly different from normal processing.
+Refer to the description of
+supported record types for details.
+
+
+If the @init
handler has read a value and has completed
+without error, the record starts in a defined state.
+That means UDF=0
, SEVR=NO_ALARM
,
+STAT=NO_ALARM
and the VAL
field contains
+the value read from the device.
+
+
+If no @init
handler is installed, VAL
and
+RVAL
fields remain untouched.
+That means they contain the value defined in the record definition,
+read from a constant INP
or DOL
field,
+or restored from a bump-less reboot system
+(e.g. autosave from the synApps package).
+
+
+
+3. I/O Intr
+
+StreamDevice supports I/O event scanning.
+This is a mode where record processing is triggered by the device
+whenever the device sends input.
+
+
+In terms of protocol execution this means:
+When the SCAN
field is set to I/O Intr
+(during iocInit
or later),
+the protocol starts without processing the record.
+With the first in
command, the protocol is suspended.
+If the device has been locked (i.e there was an out
+command earlier in the protocol), it is unlocked now.
+That means that other records can communicate to the device while
+this record is waiting for input.
+This in
command ignores replyTimeout
,
+it waits forever.
+
+
+The protocol now receives any input from the device.
+
+It also gets a copy of all input directed to other records.
+Non-matching input does not generate a mismatch
+exception .
+It just restarts the in
command until matching input
+is received.
+
+
+After receiving matching input, the protocol continues normally.
+All other in
commands are handled normally.
+When the protocol has completed, the record is processed.
+It then triggers monitors, forward links, etc.
+After the record has been processed, the protocol restarts.
+
+
+This mode is useful in two cases:
+First for devices that send data automatically without being asked.
+Second to distribute multiple values in one message to different
+records.
+In this case, one record would send a request to the device and pick
+only one value out of the reply.
+The other values are read by records in I/O Intr
mode.
+
+
+Example:
+
+
+Device dev1 has a "region of interest" (ROI) defined by
+a start value and an end value. When asked "ROI?
",
+it replies something like "ROI 17.3 58.7
",
+i.e. a string containing both values.
+
+
+We need two ai records to store the two values. Whenever record
+ROI:start
is processed, it requests ROI from the device.
+Record ROI:end
updates automatically.
+
+
+record (ai "ROI:start") {
+ field (DTYP, "stream")
+ field (INP, "@myDev.proto getROIstart dev1")
+}
+record (ai "ROI:end") {
+ field (DTYP, "stream")
+ field (INP, "@myDev.proto getROIend dev1")
+ field (SCAN, "I/O Intr")
+}
+
+
+Only one of the two protocols sends a request, but both read
+their part of the same reply message.
+
+
+getROIstart {
+ out "ROI?";
+ in "ROI %f %*f";
+}
+getROIend {
+ in "ROI %*f %f";
+}
+
+
+Note that the other value is also parsed by each protocol, but skipped
+because of the %*
format .
+Even though the getROIend
protocol may receive input
+from other requests, it silently ignores every message that does not start
+with "ROI
", followed by two floating point numbers.
+
+
+Next: Supported Record Types
+Dirk Zimoch, 2005
+
+
+
diff --git a/doc/protocol.html b/doc/protocol.html
new file mode 100644
index 0000000..56db86e
--- /dev/null
+++ b/doc/protocol.html
@@ -0,0 +1,552 @@
+
+
+
+StreamDevice: Protocol Files
+
+
+
+
+
+
+StreamDevice: Protocol Files
+
+1. General Information
+
+A protocol file describes the communication with one device type.
+It contains protocols for each function
+of the device type and variables which affect
+how the commands in a protocol work.
+It does not contain information about the individual device or the used
+communication bus.
+
+
+Each device type should have its own protocol file.
+I suggest to choose a file name that contains the name of the device type.
+Don't use spaces in the file name and keep it short.
+The file will be referenced by its name in the INP
+or OUT
link of the records which use it.
+The protocol file must be stored in one of the directories listed
+in the environment variable STREAM_PROTOCOL_PATH
+(see chapter Setup ).
+
+
+The protocol file is a plain text file.
+Everything not enclosed in quotes
+(single '
or double "
) is not case sensitive.
+This includes the names of commands ,
+protocols and variables .
+There may be any amount of whitespaces (space, tab, newline, ...) or
+comments between names, quoted strings and special
+characters, such as ={};
.
+A comment is everything starting from an unquoted #
+until the end of the line.
+
+
+Example Protocol File:
+
+# This is an example protocol file
+
+Terminator = CR LF;
+
+# Frequency is a float
+# use ai and ao records
+
+getFrequency {
+ out "FREQ?"; in "%f";
+}
+
+setFrequency {
+ out "FREQ %f";
+ @init { getFrequency; }
+}
+
+# Switch is an enum, either OFF or ON
+# use bi and bo records
+
+getSwitch {
+ out "SW?"; in "SW %{OFF|ON}";
+}
+
+setSwitch {
+ out "SW %{OFF|ON}";
+ @init { getSwitch; }
+}
+
+# Connect a stringout record to this to get
+# a generic command interface.
+# After processing finishes, the record contains the reply.
+
+debug {
+ ExtraInput = Ignore;
+ out "%s"; in "%39c"
+}
+
+
+
+2. Protocols
+
+For each function of the device type, define one protocol.
+A protocol consists of a name followed by a body in braces {}
.
+The name must be unique within the protocol file.
+It is used to reference the protocol in the
+INP
or OUT
link of the record,
+thus keep it short.
+It should describe the function of the protocol.
+It must not contain spaces or any of the characters
+,;={}()$'"\#
.
+
+
+The protocol body contains a sequence of commands and
+optionally variable assignments separated by
+;
.
+
+Referencing other protocols
+
+To save some typing, a previously defined protocol can be called inside
+another protocol like a command without parameters.
+The protocol name is replaced by the commands in the referenced protocol.
+However, this does not include any
+variable assignments or
+exception handlers from the referenced protocol.
+See the @init
handlers in the above example.
+
+Limitations
+
+The StreamDevice protocol is not a programming language.
+It has neither loops nor conditionals
+(in this version of StreamDevice ).
+However, if an error occurs, e.g. a timeout or a mismatch in input
+parsing, an exception handler can be called to
+clean up.
+
+
+
+
+3. Commands
+
+Seven different commands can be used in a protocol:
+out
, in
, wait
, event
,
+exec
, disconnect
, and connect
.
+Most protocols will consist only of a single out
command to
+write some value,
+or an out
command followed by an in
command to
+read a value.
+But there can be any number of commands in a protocol.
+
+
+ out string ;
+
+ Write output to the device.
+ The argument string may contain
+ format converters which are replaced by the
+ formatted value of the record before sending.
+
+ in string ;
+
+ Read and parse input from the device.
+ The argument string may contain
+ format converters which specify how to
+ interpret data to be put into the record.
+ Input must match the argument string.
+ Any input from the device should be consumed with an
+ in
command.
+ If a device, for example, acknowledges a setting, use an
+ in
command to check the acknowledge, even though
+ it contains no user data.
+
+ wait milliseconds ;
+
+ Just wait for some milliseconds.
+ Depending on the resolution of the timer system, the actual delay
+ can be slightly longer than specified.
+
+ event(eventcode ) milliseconds ;
+
+ Wait for event eventcode
with some timeout.
+ What an event actually means depends on the used
+ bus .
+ Some buses do not support events at all, some provide many different
+ events.
+ If the bus supports only one event, (eventcode )
+ is dispensable.
+
+ exec string ;
+
+ The argument string is passed to the IOC shell
+ as a command to execute.
+
+ disconnect;
+
+ Disconnect from the hardware.
+ This is probably not supported by all busses.
+ Any in
or out
command will automatically
+ reconnect.
+ Only records reading in
+ "I/O Intr" mode
+ will not cause a reconnect.
+
+ connect milliseconds ;
+
+ Explicitely connect to the hardware with milliseconds
+ timeout.
+ Since connection is handled automatically, this command is normally not
+ needed.
+ It may be useful after a disconnect
.
+
+
+
+
+4. Strings
+
+In a StreamDevice protocol file, strings can be written
+as quoted literals (single quotes or double quotes), as
+a sequence of bytes values, or as a combination of both.
+
+
+Examples for quoted literals are:
+"That's a string."
+'Say "Hello"'
+
+
+There is no difference between double quoted and single quoted
+literals, it just makes it easier to use quotes of the other type
+in a string.
+To break long strings into multiple lines of the protocol file,
+close the quotes before the line break and reopen them in the next line.
+Don't use a line break inside quotes.
+
+
+As arguments of out
or in
+commands , string literals can contain
+format converters .
+A format converter starts with %
and works similar
+to formats in the C functions printf() and scanf() .
+
+
+StreamDevice uses the backslash character \
to
+define some escape sequences in quoted string literals:
+\"
, \'
, \%
, and \\
+mean literal "
, '
, %
, and
+\
.
+\a
means alarm bell (ASCII code 7).
+\b
means backspace (ASCII code 8).
+\t
means tab (ASCII code 9).
+\n
means new line (ASCII code 10).
+\r
means carriage return (ASCII code 13).
+\e
means escape (ASCII code 27).
+\x
followed by up to two hexadecimal digits means a byte with
+that hex value.
+\0
followed by up to three octal digits means a byte with
+that octal value.
+\1
to \9
followed by up to two more decimal
+digits means a byte with that decimal value.
+\?
in the argument string of an in
+command matches any input byte
+\$
followed by the name of a
+protocol varible is replaced by the contents of that
+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).
+StreamDevice also defines some symbolic names for frequently
+used byte codes as aliases for the numeric byte value:
+EOT
means end of transmission (ASCII code 4).
+ACK
means acknowledge (ASCII code 6).
+BEL
means bell (ASCII code 7).
+BS
means backspace (ASCII code 8).
+HT
or TAB
mean horizontal tabulator
+(ASCII code 9).
+LF
or NL
mean line feed /
+new line (ASCII code 10).
+CR
means carriage return (ASCII code 13).
+ESC
means escape (ASCII code 27).
+DEL
means delete (ASCII code 127).
+SKIP
in the argument string of an in
+command matches any input byte.
+
+
+A single string can be built from several quoted literals and byte values
+by writing them separated by whitespaces or comma.
+
+
+Example:
+
+The following lines represent the same string:
+"Hello world\r\n"
+'Hello',0x20,"world",CR,LF
+72 101 108 108 111 32 119 111 114 108 100 13 10
+
+
+
+5. Protocol Variables
+
+StreamDevice uses three types of variables in a protocol file.
+System variables influence the behavior
+of in
and out
commands .
+Protocol arguments work like function
+arguments and can be specified in the INP
or
+OUT
link of the record.
+User variables can be defined and used
+in the protocol as abbreviations for often used values.
+
+
+System and user variables can be set in the global context of the
+protocol file or locally inside protocols.
+When set globally, a variable keeps its value until overwritten.
+When set locally, a variable is valid inside the protocol only.
+To set a variable use the syntax:
+variable = value ;
+
+
+Set variables can be referenced outside of
+quoted strings by
+$variable
or ${variable }
+and inside quoted strings by
+\$variable
or \${variable }
.
+The reference will be replaced by the value of the variable at
+this point.
+
+
+
+System variables
+
+This is a list of system variables, their default settings and
+what they influence.
+
+
+ LockTimeout = 5000;
+
+ Integer. Affects first out
command in a protocol.
+ If other records currently use the device, how many milliseconds
+ to wait for exclusive access to the device before giving up?
+
+ WriteTimeout = 100;
+
+ Integer. Affects out
commands.
+ If we have access to the device but output cannot be written
+ immediately, how many milliseconds to wait before giving up?
+
+ ReplyTimeout = 1000;
+
+ Integer. Affects in
commands.
+ Different devices need different times to calculate
+ a reply and start sending it.
+ How many milliseconds to wait for the first byte of the input
+ from the device?
+ Since several other records may be waiting to access the device
+ during this time, LockTimeout
should be larger than
+ ReplyTimeout
.
+
+ ReadTimeout = 100;
+
+ Integer. Affects in
commands.
+ The device may send input in pieces (e.g. bytes).
+ When it stops sending, how many milliseconds to wait for more
+ input bytes before giving up?
+ If InTerminator = ""
, a read timeout is not an error
+ but a valid input termination.
+
+ PollPeriod = $ReplyTimeout;
+
+ Integer. Affects first in
command in
+ I/O Intr
mode (see chapter
+ Record Processing ).
+ In that mode, some buses require periodic polling to get asynchronous
+ input if no other record executes an in
command at
+ the moment.
+ How many milliseconds to wait after last poll or last received
+ input before polling again?
+ If not set the same value as for ReplyTimeout
is
+ used.
+
+ Terminator
+
+ String. Affects out
and in
commands.
+ Most devices send and expect terminators after each message,
+ e.g. CR LF
.
+ The value of the Terminator
variable is automatically
+ appended to any output.
+ It is also used to find the end of input.
+ It is removed before the input is passed to the in
+ command.
+ If no Terminator
or InTerminator
is defined,
+ the underlying driver may use its own terminator settings.
+ For example, asynDriver defines its own terminator settings.
+
+ OutTerminator = $Terminator;
+
+ String. Affects out
commands.
+ If a device has different terminators for input and output,
+ use this for the output terminator.
+
+ InTerminator = $Terminator;
+
+ String. Affects in
commands.
+ If a device has different terminators for input and output,
+ use this for the input terminator.
+ If no Terminator
or InTerminator
is defined,
+ the underlying driver may use its own terminator settings.
+ If InTerminator = ""
, a read timeout is not an error
+ but a valid input termination.
+
+ MaxInput = 0;
+
+ Integer. Affects in
commands.
+ Some devices don't send terminators but always send a fixed message
+ size. How many bytes to read before terminating input even without
+ input terminator or read timeout?
+ The value 0
means "infinite".
+
+ Separator = "";
+
+ String. Affects out
and in
commands.
+ When formatting or parsing array values in a format converter
+ (see formats and
+ waveform record ), what string
+ to write or to expect between values?
+ If the first character of the Separator
is a
+ space, it matches any number of any whitespace characters in
+ an in
command.
+
+ ExtraInput = Error;
+
+ Error
or Ignore
.
+ Affects in
commands.
+ Normally, when input parsing has completed, any bytes left in the
+ input are treated as parse error.
+ If extra input bytes should be ignored, set
+ ExtraInput = Ignore;
+
+
+
+
+Protocol arguments
+
+Sometimes, protocols differ only very little.
+In that case it can be convenient to write only one protocol
+and use protocol arguments for the difference.
+For example a motor controller for the 3 axes X, Y, Z requires
+three protocols to set a position.
+
+
+moveX { out "X GOTO %d"; }
+moveY { out "Y GOTO %d"; }
+moveZ { out "Z GOTO %d"; }
+
+
+It also needs three versions of any other protocol.
+That means basically writing everything three times.
+To make this easier, protocol arguments can be used:
+
+
+move { out "\$1 GOTO %d"; }
+
+
+Now, the protocol can be references in the OUT
link
+of three different records as move(X)
,
+move(Y)
and move(Z)
.
+Up to 9 parameters, referenced as $1
... $9
+can be specified in parentheses, separated by comma.
+The variable $0
is replaced by the name of the protocol.
+
+
+
+User variables
+
+User defined variables are just a means to save some typing.
+Once set, a user variable can be referenced later in the protocol.
+
+
+f = "FREQ"; # sets f to "FREQ" (including the quotes)
+f1 = $f " %f"; # sets f1 to "FREQ %f"
+
+getFrequency {
+ out $f "?"; # same as: out "FREQ?";
+ in $f1; # same as: in "FREQ %f";
+}
+
+setFrequency {
+ out $f1; # same as: out "FREQ %f";
+}
+
+
+6. Exception Handlers
+
+When an error happens, an exception handler may be called.
+Exception handlers are a kind of sub-protocols in a protocol.
+They consist of the same set of commands and are intended to
+reset the device or to finish the protocol cleanly in case of
+communication problems.
+Like variables, exception handlers can be defined globally or
+locally.
+Globally defined handlers are used for all following protocols
+unless overwritten by a local handler.
+There is a fixed set of exception handler names starting with
+@
.
+
+
+ @mismatch
+
+ Called when input does not match in an in
command.
+ It means that the device has sent something else than what the
+ protocol expected.
+ If the handler starts with an in
command, then this
+ command reparses the old input from the unsuccessful in
.
+ Error messages from the unsuccessful in
are suppressed.
+ Nevertheless, the record will end up in INVALID/CALC
+ state (see chapter Record Processing ).
+
+ @writetimeout
+
+ Called when a write timeout occurred in an out
command.
+ It means that output cannot be written to the device.
+ Note that out
commands in the handler are
+ also likely to fail in this case.
+
+ @replytimeout
+
+ Called when a reply timeout occurred in an in
command.
+ It means that the device does not send any data.
+ Note that in
commands in the handler are
+ also likely to fail in this case.
+
+ @readtimeout
+
+ Called when a read timeout occurred in an in
command.
+ It means that the device stopped sending data unexpectedly after
+ sending at least one byte.
+
+ @init
+
+ Not really an exception but formally specified in the same syntax.
+ This handler is called from iocInit
during record
+ initialization.
+ It can be used to initialize an output record with a value read from
+ the device.
+ Also see chapter Record Processing .
+
+
+Example:
+
+setPosition {
+ out "POS %f";
+ @init { out "POS?"; in "POS %f"; }
+}
+
+
+After executing the exception handler, the protocol terminates.
+If any exception occurs within an exception handler, no other handler
+is called but the protocol terminates immediately.
+An exception handler uses all system variable
+settings from the protocol in which the exception occurred.
+
+
+Next: Format Converters
+Dirk Zimoch, 2006
+
+
+
diff --git a/doc/recordinterface.html b/doc/recordinterface.html
new file mode 100644
index 0000000..9cbc871
--- /dev/null
+++ b/doc/recordinterface.html
@@ -0,0 +1,20 @@
+
+
+
+StreamDevice: Record API
+
+
+
+
+
+
+StreamDevice: Record API
+
+Sorry, this documentation is still missing.
+
+
+Dirk Zimoch, 2006
+
+
+
diff --git a/doc/recordtypes.html b/doc/recordtypes.html
new file mode 100644
index 0000000..b1acb28
--- /dev/null
+++ b/doc/recordtypes.html
@@ -0,0 +1,54 @@
+
+
+
+StreamDevice: Record Types
+
+
+
+
+
+
+StreamDevice: Record Types
+
+Supported Record Types
+
+StreamDevice comes with support for all standard record types
+in EPICS base which can have device support.
+
+
+There is a separate page for each supported record type:
+aai
+aao
+ai
+ao
+bi
+bo
+mbbi
+mbbo
+mbbiDirect
+mbboDirect
+longin
+longout
+stringin
+stringout
+waveform
+calcout
+scalcout
+
+
+Each page describes which record fields are used in input and output
+for different format data types
+during normal record processing
+and initialization .
+
+
+It is also possible to
+write support for other recordtypes .
+
+
+
+Dirk Zimoch, 2005
+
+
+
diff --git a/doc/scalcout.html b/doc/scalcout.html
new file mode 100644
index 0000000..892ea25
--- /dev/null
+++ b/doc/scalcout.html
@@ -0,0 +1,101 @@
+
+
+
+StreamDevice: scalcout Records
+
+
+
+
+
+
+StreamDevice: scalcout Records
+
+
+Note: The scalcout record is part of the calc module of
+the synApps package.
+Device support for scalcout records is only available for calc
+module release 2-4 or higher.
+You also need the synApps modules genSub and sscan to
+build calc .
+
+
+Up to release 2-6 (synApps release 5.1), the scalcout record needs a fix.
+In sCalcout.c at the end of init_record
add
+before the final return(0)
:
+
+
+ if(pscalcoutDSET->init_record ) {
+ return (*pscalcoutDSET->init_record)(pcalc);
+ }
+
+
+Normal Operation
+
+Different record fields are used for output and input. The variable
+x
stands for the written or read value.
+
+
+ DOUBLE format (e.g. %f
):
+
+ Output: x =OVAL
+ Input: VAL=x
+ Note that the record calculates OVAL
from CALC
+ or OCAL
depending on DOPT
.
+
+ LONG format (e.g. %i
):
+
+ Output: x =int(OVAL)
+ Input: VAL=x
+
+ ENUM format (e.g. %{
):
+
+ Output: x =int(OVAL)
+ Input: VAL=x
+
+ STRING format (e.g. %s
):
+
+ Output: x =OSV
+ Input: SVAL=x
+
+
+
+ For scalcout records, it is probably more useful to access fields
+ A
to L
and AA
to LL
+ directly (e.g. "%(A)f"
or "%(BB)s"
).
+ However, even if OVAL
is not used, it is calculated by the
+ record. Thus, CALC
must always contain a valid expression
+ (e.g. "0"
).
+
+
+Initialization
+
+ During initialization , the @init
handler is executed, if
+ present. All format converters work like in normal operation.
+
+
+
+
+aai
+aao
+ai
+ao
+bi
+bo
+mbbi
+mbbo
+mbbiDirect
+mbboDirect
+longin
+longout
+stringin
+stringout
+waveform
+calcout
+
+Dirk Zimoch, 2005
+
+
+
diff --git a/doc/setup.html b/doc/setup.html
new file mode 100644
index 0000000..9a6c33c
--- /dev/null
+++ b/doc/setup.html
@@ -0,0 +1,290 @@
+
+
+
+StreamDevice: Setup
+
+
+
+
+
+
+StreamDevice: Setup
+
+
+1. Prerequisites
+
+StreamDevice requires either
+EPICS base R3.14.6 or higher or
+EPICS base R3.13.7 or higher .
+How to use StreamDevice on EPICS R3.13 is described on a
+separate page .
+Because StreamDevice comes with an interface to
+asynDriver version R4-3 or higher as the
+underlying driver layer,
+you should have asynDriver installed first.
+
+
+StreamDevice has support for the
+scalcout record from the
+calc module of synApps .
+Up to calc release R2-6 (synApps release R5_1),
+the scalcout record needs a fix.
+(See separate scalcout page .)
+
+
+Up to release R3.14.8.2, a fix in EPICS base is required to build
+StreamDevice on Windows (not cygwin).
+In src/iocsh/iocsh.h , add the following line
+and rebuild base.
+
+
+epicsShareFunc int epicsShareAPI iocshCmd(const char *command);
+
+
+
+Make sure that the asyn library and the calc module of
+synApps can be found, e.g. by
+adding ASYN
+and (if installed) CALC
or SYNAPPS
+to your <top>/configure/RELEASE file:
+
+ASYN=/home/epics/asyn/4-5
+CALC=/home/epics/synApps/calc/2-7
+
+
+For details on <top> directories and RELEASE files,
+please refer to the
+IOC Application Developer's Guide chapter 4:
+EPICS Build Facility.
+
+
+
+2. Build the StreamDevice Library
+
+Unpack the
+StreamDevice package in a <top> directory
+of your application build area.
+(You might probably have done this already.)
+Go to the newly created StreamDevice directory
+and run make (or gmake ).
+This will create and install the stream library and the
+stream.dbd file.
+
+
+3. Build an Application
+
+To use StreamDevice , your application must be built with the
+asyn and stream libraries and must load
+asyn.dbd and stream.dbd .
+
+
+Include the following lines in your application Makefile:
+
+
+PROD_LIBS += stream
+PROD_LIBS += asyn
+
+
+Include the following lines in your xxxAppInclude.dbd file to use
+stream and asyn with serial lines and IP sockets:
+
+
+include "base.dbd"
+include "stream.dbd"
+include "asyn.dbd"
+registrar(drvAsynIPPortRegisterCommands)
+registrar(drvAsynSerialPortRegisterCommands)
+
+
+You can find an example application in the streamApp
+subdirectory.
+
+
+
+4. The Startup Script
+
+StreamDevice is based on protocol files .
+To tell StreamDevice where to search for protocol files,
+set the environment variable STREAM_PROTOCOL_PATH
to a
+list of directories to search.
+On Unix and vxWorks systems, directories are separated by :
,
+on Windows systems by ;
.
+The default value is STREAM_PROTOCOL_PATH=.
,
+i.e. the current directory.
+
+
+Also configure the buses (in asynDriver terms: ports) you want
+to use with StreamDevice .
+You can give the buses any name you want, like COM1 or
+socket , but I recommend to use names related to the
+connected device.
+
+Example:
+
+A power supply with serial communication (9600 baud, 8N1) is connected to
+/dev/ttyS1 .
+The name of the power supply is PS1 .
+Protocol files are either in the current working directory or in the
+../protocols directory.
+
+
+Then the startup script must contain lines like this:
+
+
+epicsEnvSet ("STREAM_PROTOCOL_PATH", ".:../protocols")
+
+drvAsynSerialPortConfigure ("PS1","/dev/ttyS1")
+asynSetOption ("PS1", 0, "baud", "9600")
+asynSetOption ("PS1", 0, "bits", "8")
+asynSetOption ("PS1", 0, "parity", "none")
+asynSetOption ("PS1", 0, "stop", "1")
+asynSetOption ("PS1", 0, "clocal", "Y")
+asynSetOption ("PS1", 0, "crtscts", "N")
+
+
+If the power supply was connected via telnet-style TCP/IP
+at address 192.168.164.10 on port 23,
+the startupscript would contain:
+
+
+epicsEnvSet ("STREAM_PROTOCOL_PATH", ".:../protocols")
+
+drvAsynIPPortConfigure ("PS1", "192.168.164.10:23")
+
+
+
+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:
+
+
+epicsEnvSet ("STREAM_PROTOCOL_PATH", ".:../protocols")
+
+vxi11Configure ("PS1","192.168.164.10",1,1000,"hpib")
+
+
+
+
+5. The Protocol File
+
+For each different type of hardware, create a protocol file
+which defines protocols for all needed functions of the device.
+The file name is arbitrary, but I recommend that it contains
+the device type.
+It must not contain spaces and should be short.
+During iocInit
, streamDevice 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
+STREAM_PROTOCOL_PATH
.
+
+Example:
+
+PS1 is an ExamplePS power supply.
+It communicates via ASCII strings which are terminated by
+<carriage return> <line feed> (ASCII codes 13, 10).
+The output current can be set by sending a string like
+"CURRENT 5.13"
.
+When asked with the string "CURRENT?"
, the device returns
+the last set value in a string like "CURRENT 5.13 A"
.
+
+
+Normally, an analog output record should write its value to the device.
+But during startup, the record should be initialized from the the device.
+The protocol file ExamplePS.proto defines the protocol
+setCurrent
.
+
+
+Terminator = CR LF;
+
+setCurrent {
+ out "CURRENT %.2f";
+ @init {
+ out "CURRENT?";
+ in "CURRENT %f A";
+ }
+}
+
+
+
+Reloading the Protocol File
+
+During development, the protocol files might change frequently.
+To prevent restarting the IOC all the time, it is possible to reload
+the protocol file of one or all records with the shell function
+streamReload("record ")
.
+If "record "
is not given, all records using
+StreamDevice reload their protocols.
+Furthermore, the streamReloadSub
function can be used
+with a subroutine record to reload all protocols.
+
+
+Reloading the protocol file aborts currently running protocols.
+This might set SEVR=INVALID
and STAT=UDF
.
+If a record can't reload its protocol file (e.g. because of a syntax
+error), it stays INVALID
/UDF
until a valid
+protocol is loaded.
+
+
+
+See the next chapter for protocol files in depth.
+
+
+
+6. Configure the Records
+
+To make a record use StreamDevice , set its DTYP
field to
+"stream"
.
+The INP
or OUT
link has the form
+"@file protocol bus [address [parameters ]]"
.
+
+
+Here, file
is the name of the protocol file and
+protocol
is the name of a protocol defined in this file.
+If the protocol requires arguments ,
+specify them enclosed in parentheses:
+protocol (arg1,arg2,... )
.
+
+
+The communication channel is specified with bus
and
+addr
.
+If the bus does not have addresses, addr
is dispensable.
+Optional parameters
are passed to the bus driver.
+
+
+Example:
+
+Create an output record to set the current of PS1 .
+Use protocol setCurrent from file ExamplePS.proto .
+The bus is called PS1 like the device.
+
+
+
+record (ao, "PS1:I-set")
+{
+ field (DESC, "Set current of PS1")
+ field (DTYP, "stream")
+ field (OUT, "@ExamplePS.proto setCurrent PS1")
+ field (EGU, "A")
+ field (PREC, "2")
+ field (DRVL, "0")
+ field (DRVH, "60")
+ field (LOPR, "0")
+ field (HOPR, "60")
+}
+
+
+
+Next: Protocol Files
+Dirk Zimoch, 2007
+
+
+
diff --git a/doc/sls_icon.ico b/doc/sls_icon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..9a556f59d01cc38e03162415ee8f79bb9a2ee908
GIT binary patch
literal 318
zcma)%yA8uI3`C!Z7;s6ra80Yy7~aeoAtQJcuaeA=UOu%6{5j!C5RafvkhI)g`-75T
z1d4DGqScR7Wz=OkvM=lTrie*_%uH$UP_9G_ZG0lPpiJbzFx_QG=*%Y0RrP^iN(XZf
R&zHt-(7*NM2iEU@?+ylPCt&~p
literal 0
HcmV?d00001
diff --git a/doc/space.gif b/doc/space.gif
new file mode 100644
index 0000000000000000000000000000000000000000..e1e3800acee6970a9798f55d58fb8276e2c73ecb
GIT binary patch
literal 807
zcmZ?wbhEHbWMp7u_|5
+
+
+StreamDevice
+
+
+
+
+
+
+EPICS StreamDevice Documentation
+
+What is StreamDevice ?
+
+StreamDevice is a generic
+EPICS
+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.
+StreamDevice comes with an interface to
+asynDriver
+but can be extended to
+support other bus drivers .
+
+
+If the device can be controlled with strings like
+"RF:FREQ 499.655 MHZ
", StreamDevice can be used.
+How the strings exactly look like is defined in protocols .
+Formatting and interpretation of values is done with
+format converters similar to those
+known from the C functions printf() and scanf() .
+To support other formats, it is possible to
+write your own converters .
+
+
+Each record with StreamDevice support runs one protocol
+to read or write its value.
+All protocols are defined in
+protocol files in plain ASCII text.
+No compiling is necessary to change a protocol or to support new devices.
+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
+processed .
+It does not provide loops or branches.
+
+
+StreamDevice supports all
+standard records of EPICS base
+which can have device support.
+It is also possible to
+write support for new record types .
+
+
+What is StreamDevice not?
+
+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 StreamDevice only for the
+primitive commands.
+
+
+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.
+
+
+Recommended Readings
+
+IOC Application Developer's Guide (PDF)
+
+
+EPICS Record Reference Manual
+
+
+
+Next: Setup
+Dirk Zimoch, 2006
+
+
+
diff --git a/doc/stream.js b/doc/stream.js
new file mode 100644
index 0000000..da8aaf0
--- /dev/null
+++ b/doc/stream.js
@@ -0,0 +1,5 @@
+if (parent.head) {
+ parent.head.document.getElementById('subtitle').innerHTML=document.title;
+ parent.document.title=document.title;
+ document.getElementsByTagName('h1')[0].style.display="none";
+}
diff --git a/doc/stringin.html b/doc/stringin.html
new file mode 100644
index 0000000..42b48a7
--- /dev/null
+++ b/doc/stringin.html
@@ -0,0 +1,67 @@
+
+
+
+StreamDevice: stringin Records
+
+
+
+
+
+
+StreamDevice: stringin Records
+
+Normal Operation
+
+The variable x
stands for the
+written or read value.
+
+
+ DOUBLE format (e.g. %f
):
+
+ Not allowed.
+
+ LONG format (e.g. %i
):
+
+ Not allowed.
+
+ ENUM format (e.g. %{
):
+
+ Not allowed.
+
+ STRING format (e.g. %s
):
+
+ Output: x =VAL
+ Input: VAL=x
+
+
+
+Initialization
+
+ During initialization , the @init
handler is executed, if
+ present. All format converters work like in normal operation.
+
+
+
+
+aai
+aao
+ai
+ao
+bi
+bo
+mbbi
+mbbo
+mbbiDirect
+mbboDirect
+longin
+longout
+stringout
+waveform
+calcout
+scalcout
+
+Dirk Zimoch, 2005
+
+
+
diff --git a/doc/stringout.html b/doc/stringout.html
new file mode 100644
index 0000000..a57bc54
--- /dev/null
+++ b/doc/stringout.html
@@ -0,0 +1,67 @@
+
+
+
+StreamDevice: stringout Records
+
+
+
+
+
+
+StreamDevice: stringout Records
+
+Normal Operation
+
+The variable x
stands for the
+written or read value.
+
+
+ DOUBLE format (e.g. %f
):
+
+ Not allowed.
+
+ LONG format (e.g. %i
):
+
+ Not allowed.
+
+ ENUM format (e.g. %{
):
+
+ Not allowed.
+
+ STRING format (e.g. %s
):
+
+ Output: x =VAL
+ Input: VAL=x
+
+
+
+Initialization
+
+ During initialization , the @init
handler is executed, if
+ present. All format converters work like in normal operation.
+
+
+
+
+aai
+aao
+ai
+ao
+bi
+bo
+mbbi
+mbbo
+mbbiDirect
+mbboDirect
+longin
+longout
+stringin
+waveform
+calcout
+scalcout
+
+Dirk Zimoch, 2005
+
+
+
diff --git a/doc/tipsandtricks.html b/doc/tipsandtricks.html
new file mode 100644
index 0000000..092d5f1
--- /dev/null
+++ b/doc/tipsandtricks.html
@@ -0,0 +1,297 @@
+
+
+
+StreamDevice: Tips and Tricks
+
+
+
+
+
+
+StreamDevice: Tips and Tricks
+
+
+I have many almost identical protocols
+
+You can give arguments to a protocol.
+In the INP
or OUT
link, write:
+
+
+
+field (OUT, "@protocolfile protocol(arg1,arg2,arg3) bus")
+
+
+
+In the protocol, reference arguments as $1 $2 $3
or inside strings
+as "\$1 \$2 \$3"
.
+
+
+
+moveaxis {out "move\$1 %.6f";}
+field (OUT, "@motor.proto moveaxis(X) motor1")
+
+
+
+
+readpressure {out 0x02 0x00 $1; in 0x82 0x00 $1 "%2r";}
+field (INP, "@vacuumgauge.proto readpressure(0x84) gauge3")
+
+
+
+
+I have a device that sends unsolicited data
+
+Use I/O Intr
processing .
+The record receives any input and processes only when the input matches.
+
+
+
+read {in "new value = %f";}
+
+
+
+
+record (ai, "$(RECORD)") {
+ field (DTYP, "stream")
+ field (INP, "@(DEVICETYPE).proto read $(BUS)")
+ field (SCAN, "I/O Intr")
+}
+
+
+
+
+I have a device that sends multi-line messages
+
+Here is the value:
+3.1415
+
+
+Use as many in
commands as you get input lines.
+
+
+
+read_value {in "Here is the value:"; in "%f";}
+
+
+
+
+I need to write more than one value in one message
+
+There is more than one solution to this problem.
+Different approaches have different requirements.
+
+A) All values have the same type and are separated by the same string
+
+Use array records (e.g. waveform ,
+aao ).
+
+
+
+array_out {separator=", "; out "an array: (%.2f)";}
+
+
+
+The format %.2f
is repeated for each element of the array.
+All elements are separated by ", "
.
+Output will look like this:
+
+an array: (3.14, 17.30, -12.34)
+
+
+B) We have up to 12 numeric values
+
+Use a calcout record and
+field references in the format.
+
+
+
+write_ABC {out "A=%(A).2f B=%(B).6f C=%(C).0f";}
+
+
+
+You must specify a valid expression in the CALC
field
+even if you don't use it.
+
+
+
+record (calcout, "$(RECORD)") {
+ field (INPA, "$(A_RECORD)")
+ field (INPB, "$(B_RECORD)")
+ field (INPC, "$(C_RECORD)")
+ field (CALC, "0")
+ field (DTYP, "stream")
+ field (OUT, "@(DEVICETYPE).proto write_ABC $(BUS)")
+}
+
+
+C) Values are in other records on the same IOC
+
+Use record references in the format.
+
+
+
+acquire {out 'ACQUIRE "%(\$1:directory.VAL)s/%s",%(\$1:time.VAL).3f;';}
+
+
+
+You must specify the full record.FIELD
name,
+even for VAL
fields.
+To avoid record names in protocol files use
+protocol arguments .
+In the link, specify the record name or just the basename of the
+other records (device name) in parentheses.
+
+
+
+record (stringout, "$(DEVICE):getimage") {
+ field (DTYP, "stream")
+ field (OUT, "@(DEVICETYPE).proto acquire($(DEVICE)) $(BUS)")
+}
+
+
+
+
+I need to read more than one value from one message
+
+Again, there is more than one solution to this problem.
+
+A) All values have the same type and are separated by the same string
+
+Use array records (e.g. waveform ,
+aai ).
+
+
+
+array_in {separator=","; in "array = (%f)";}
+
+
+
+The format %f
is repeated for each element of the array.
+A ","
is expected beween element.
+Input may look like this:
+
+
+array = (3.14, 17.30, -12.34)
+
+
+B) The message and the values in it can be filtered easily
+
+Use I/O Intr
processing
+and value skipping (%*
)
+
+
+
+read_A {out "GET A,B"; in "A=%f, B=%*f";}
+read_B {in "A=%*f, B=%f";}
+
+
+
+
+record (ai, "$(DEVICE):A") {
+ field (DTYP, "stream")
+ field (INP, "@(DEVICETYPE).proto read_A $(BUS)")
+ field (SCAN, "1 second")
+}
+record (ai, "$(DEVICE):B") {
+ field (DTYP, "stream")
+ field (INP, "@(DEVICETYPE).proto read_B $(BUS)")
+ field (SCAN, "I/O Intr")
+}
+
+
+
+Record A actively requests values every second.
+The reply contains values A and B.
+Record A filters only value A from the input and ignores value B
+by using the *
flag.
+Nevertheless, a complete syntax check is performed: B must be a
+valid floating point number.
+Record B is I/O Intr
and gets (a copy of) any input, including
+input that was directed to record A.
+If it finds a matching string it ignores value A, reads value B and
+then processes.
+Any non-matching input is ignored by record B.
+
+C) Values should be stored in other records on the same IOC
+
+Use record references in the format.
+To avoid record names in protocol files, use
+protocol arguments .
+
+
+
+read_AB {out "GET A,B"; in "A=%f, B=%(\$1.VAL)f";}
+
+
+
+
+record (ai, "$(DEVICE):A") {
+ field (DTYP, "stream")
+ field (INP, "@(DEVICETYPE).proto read_AB($(DEVICE):B) $(BUS)")
+ field (SCAN, "1 second")
+}
+record (ai, "$(DEVICE):B") {
+}
+
+
+
+Whenever record A reads input, it stores the first value in its own VAL field
+as usual and the second in the VAL field of record B.
+Because the VAL field of record B has the PP attribute, this automatically
+processes record B.
+
+
+
+I have a device that sends mixed data types: numbers and strings
+
+Use a @mismatch
+exception handler and
+record references in the format.
+To avoid record names in protocol files, use
+protocol arguments .
+
+Example
+
+When asked "CURRENT?
", the device send something like
+"CURRENT 3.24 A
" or a message like
+"device switched off
".
+
+
+
+read_current {out "CURRENT?"; in "CURRENT %f A"; @mismatch {in "%(\1.VAL)39c";}}
+
+
+
+
+record (ai, "$(DEVICE):readcurrent") {
+ field (DTYP, "stream")
+ field (INP, "@(DEVICETYPE).proto read_current($(DEVICE):message) $(BUS)")
+}
+record (stringin, "$(DEVICE):message") {
+}
+
+
+
+After processing the readcurrent record, you can see from
+SEVR/STAT if the read was successful or not.
+With some more records, you can clean the message record if SEVR is not INVALID.
+
+
+record (calcout, "$(DEVICE):clean_1") {
+ field (INPA, "$(DEVICE):readcurrent.SEVR CP")
+ field (CALC, "A!=2")
+ field (OOPT, "When Non-zero")
+ field (OUT, "$(DEVICE):clean_2.PROC")
+}
+record (stringout, "$(DEVICE):clean_2") {
+ field (VAL, "OK")
+ field (OUT, "$(DEVICE):message PP")
+}
+
+
+
+Dirk Zimoch, 2007
+
+
+
diff --git a/doc/waveform.html b/doc/waveform.html
new file mode 100644
index 0000000..8614972
--- /dev/null
+++ b/doc/waveform.html
@@ -0,0 +1,126 @@
+
+
+
+StreamDevice: waveform Records
+
+
+
+
+
+
+StreamDevice: waveform Records
+
+Normal Operation
+
+With waveform records, the format converter is applied to
+each element. Between the elements, a separator is printed
+or expected as specified by the Separator
+variable in the
+protocol.
+When parsing input, a space as the first character of the
+Separator
matches any number of any whitespace
+characters.
+
+
+During input, a maximum of NELM
elements is
+read and NORD
is updated accordingly.
+Parsing of elements stops when the separator does not match,
+conversion fails, or the end of the input is reached.
+A minimum of one element must be available.
+
+
+During output, the first NORD
elements are
+written.
+
+
+The format data type must be convertible to or from the type
+specified in the FTVL
field.
+The variable x[i]
stands for one element of
+the written or read value.
+
+
+ DOUBLE format (e.g. %f
):
+
+ Output: x[i] =double(VAL[i])
+ FTVL
can be "DOUBLE"
, "FLOAT"
,
+ "LONG"
, "ULONG"
, "SHORT"
,
+ "USHORT"
, "CHAR"
, "UCHAR"
,
+ or "ENUM"
(which is treated as "USHORT"
).
+ Input: VAL[i]=FTVL(x[i] )
+ FTVL
must be "FLOAT"
or "DOUBLE"
+
+ LONG or ENUM format (e.g. %i
or %{
):
+
+ Output: x[i] =long(VAL[i])
+ FTVL
can be
+ "LONG"
, "ULONG"
, "SHORT"
,
+ "USHORT"
, "CHAR"
, "UCHAR"
,
+ or "ENUM"
(which is treated as "USHORT"
).
+ Signed values are sign-extended to long, unsigned values are
+ zero-extended to long before converting them.
+ Input: VAL[i]=FTVL(x[i])
+ FTVL
can be "DOUBLE"
, "FLOAT"
,
+ "LONG"
, "ULONG"
, "SHORT"
,
+ "USHORT"
, "CHAR"
, "UCHAR"
,
+ or "ENUM"
(which is treated as "USHORT"
).
+ The value is truncated to the least significant bytes if
+ FTVL
has a smaller data size than long
.
+
+ STRING format (e.g. %s
):
+
+
+ If FTVL=="STRING"
:
+
+ Output: x[i] =VAL[i]
+ Input: VAL[i]=x[i]
+ Note that this is an array of strings, not an array of characters.
+
+ If FTVL=="CHAR"
or FTVL="UCHAR"
:
+
+ In this case, the complete waveform is treated as a large
+ single string of size NORD
.
+ No separators are printed or expected.
+ Output: x =range(VAL,0,NORD)
+ The first NORD
characters are printed,
+ which might be less than NELM
.
+ Input: VAL=x , NORD=length(x )
+ A maximum of NELM-1
characters can be read.
+ NORD
is updated to the index of the first of the
+ trailing zeros.
+ Usually, this is the same as the string length.
+
+
+ Other values of FTVL
are not allowed for this format.
+
+
+
+Initialization
+
+ During initialization , the @init
handler is executed, if
+ present. All format converters work like in normal operation.
+
+
+
+
+aai
+aao
+ai
+ao
+bi
+bo
+mbbi
+mbbo
+mbbiDirect
+mbboDirect
+longin
+longout
+stringin
+stringout
+calcout
+scalcout
+
+Dirk Zimoch, 2005
+
+
+
diff --git a/src/AsynDriverInterface.cc b/src/AsynDriverInterface.cc
new file mode 100644
index 0000000..11b4e28
--- /dev/null
+++ b/src/AsynDriverInterface.cc
@@ -0,0 +1,1324 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is the interface to asyn driver 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 "StreamBusInterface.h"
+#include "StreamError.h"
+#include "StreamBuffer.h"
+
+#ifdef EPICS_3_14
+#include
+#include
+#include
+#else
+#include
+#include
+#include
+extern "C" {
+#include
+}
+#endif
+
+#include
+#include
+#include
+#include
+#include
+
+/* How things are implemented:
+
+synchonous io:
+
+lockRequest()
+ pasynManager->blockProcessCallback(),
+ pasynCommon->connect() only if
+ lockTimeout is unlimited (0) and port is not yet connected
+ pasynManager->queueRequest()
+ when request is handled
+ lockCallback(StreamIoSuccess)
+ if request fails
+ lockCallback(StreamIoTimeout)
+
+writeRequest()
+ pasynManager->queueRequest()
+ when request is handled
+ pasynOctet->flush()
+ pasynOctet->writeRaw()
+ if writeRaw() times out
+ writeCallback(StreamIoTimeout)
+ if writeRaw fails otherwise
+ writeCallback(StreamIoFault)
+ if writeRaw succeeds and all bytes have been written
+ writeCallback(StreamIoSuccess)
+ if not all bytes can be written
+ pasynManager->queueRequest() to write next part
+ if request fails
+ writeCallback(StreamIoTimeout)
+
+readRequest()
+ pasynManager->queueRequest()
+ when request is handled
+ optionally: pasynOctet->setInputEos()
+ pasynOctet->read()
+ if time out at the first byte
+ readCallback(StreamIoNoReply)
+ if time out at next bytes
+ readCallback(StreamIoTimeout,buffer,received)
+ if other fault
+ readCallback(StreamIoFault,buffer,received)
+ if success and reason is ASYN_EOM_END or ASYN_EOM_EOS
+ readCallback(StreamIoEnd,buffer,received)
+ if success and no end detected
+ readCallback(StreamIoSuccess,buffer,received)
+ if readCallback() has returned true (wants more input)
+ loop back to the pasynOctet->read() call
+ if request fails
+ readCallback(StreamIoFault)
+
+unlock()
+ call pasynManager->unblockProcessCallback()
+
+asynchonous input support ("I/O Intr"):
+
+pasynOctet->registerInterruptUser(...,intrCallbackOctet,...) is called
+initially. This calls intrCallbackOctet() every time input is received,
+but only if someone else is doing a read. Thus, if nobody reads
+something, arrange for periodical read polls.
+
+*/
+
+extern "C" {
+static void handleRequest(asynUser*);
+static void handleTimeout(asynUser*);
+static void intrCallbackOctet(void* pvt, asynUser *pasynUser,
+ char *data, size_t numchars, int eomReason);
+static void intrCallbackInt32(void* pvt, asynUser *pasynUser,
+ epicsInt32 data);
+static void intrCallbackUInt32(void* pvt, asynUser *pasynUser,
+ epicsUInt32 data);
+}
+
+enum IoAction {
+ None, Lock, Write, Read, AsyncRead, AsyncReadMore,
+ ReceiveEvent, Connect, Disconnect
+};
+
+static const char* ioActionStr[] = {
+ "None", "Lock", "Write", "Read",
+ "AsyncRead", "AsyncReadMore",
+ "ReceiveEvent", "Connect", "Disconnect"
+};
+
+static const char* asynStatusStr[] = {
+ "asynSuccess", "asynTimeout", "asynOverflow", "asynError"
+};
+
+static const char* eomReasonStr[] = {
+ "NONE", "CNT", "EOS", "CNT+EOS", "END", "CNT+END", "EOS+END", "CNT+EOS+END"
+};
+
+class AsynDriverInterface : StreamBusInterface
+#ifdef EPICS_3_14
+ , epicsTimerNotify
+#endif
+{
+ asynUser* pasynUser;
+ asynCommon* pasynCommon;
+ void* pvtCommon;
+ asynOctet* pasynOctet;
+ void* pvtOctet;
+ void* intrPvtOctet;
+ asynInt32* pasynInt32;
+ void* pvtInt32;
+ void* intrPvtInt32;
+ asynUInt32Digital* pasynUInt32;
+ void* pvtUInt32;
+ void* intrPvtUInt32;
+ asynGpib* pasynGpib;
+ void* pvtGpib;
+ IoAction ioAction;
+ double lockTimeout;
+ double writeTimeout;
+ double readTimeout;
+ double replyTimeout;
+ long expectedLength;
+ unsigned long eventMask;
+ unsigned long receivedEvent;
+ StreamBuffer inputBuffer;
+ const char* outputBuffer;
+ size_t outputSize;
+ int peeksize;
+#ifdef EPICS_3_14
+ epicsTimerQueueActive* timerQueue;
+ epicsTimer* timer;
+#else
+ WDOG_ID timer;
+ CALLBACK timeoutCallback;
+#endif
+
+ AsynDriverInterface(Client* client);
+ ~AsynDriverInterface();
+
+ // StreamBusInterface methods
+ bool lockRequest(unsigned long lockTimeout_ms);
+ bool unlock();
+ bool writeRequest(const void* output, size_t size,
+ unsigned long writeTimeout_ms);
+ bool readRequest(unsigned long replyTimeout_ms,
+ unsigned long readTimeout_ms, long expectedLength, bool async);
+ bool acceptEvent(unsigned long mask, unsigned long replytimeout_ms);
+ bool supportsEvent();
+ bool supportsAsyncRead();
+ bool connectRequest(unsigned long connecttimeout_ms);
+ bool disconnect();
+ void finish();
+
+#ifdef EPICS_3_14
+ // epicsTimerNotify methods
+ epicsTimerNotify::expireStatus expire(const epicsTime &);
+#else
+ static void expire(CALLBACK *pcallback);
+#endif
+
+ // local methods
+ void timerExpired();
+ bool connectToBus(const char* busname, int addr);
+ void lockHandler();
+ void writeHandler();
+ void readHandler();
+ void connectHandler();
+ void disconnectHandler();
+ bool connectToAsynPort();
+ void asynReadHandler(const char *data, int numchars, int eomReason);
+ asynQueuePriority priority() {
+ return static_cast
+ (StreamBusInterface::priority());
+ }
+ void startTimer(double timeout) {
+#ifdef EPICS_3_14
+ timer->start(*this, timeout);
+#else
+ callbackSetPriority(priority(), &timeoutCallback);
+ wdStart(timer, (int)((timeout+1)*sysClkRateGet())-1,
+ reinterpret_cast(callbackRequest),
+ reinterpret_cast(&timeoutCallback));
+#endif
+ }
+ void cancelTimer() {
+#ifdef EPICS_3_14
+ timer->cancel();
+#else
+ wdCancel(timer);
+#endif
+ }
+
+ // asynUser callback functions
+ friend void handleRequest(asynUser*);
+ friend void handleTimeout(asynUser*);
+ friend void intrCallbackOctet(void* pvt, asynUser *pasynUser,
+ char *data, size_t numchars, int eomReason);
+ friend void intrCallbackInt32(void* pvt, asynUser *pasynUser,
+ epicsInt32 data);
+ friend void intrCallbackUInt32(void* pvt, asynUser *pasynUser,
+ epicsUInt32 data);
+public:
+ // static creator method
+ static StreamBusInterface* getBusInterface(Client* client,
+ const char* busname, int addr, const char* param);
+};
+
+RegisterStreamBusInterface(AsynDriverInterface);
+
+AsynDriverInterface::
+AsynDriverInterface(Client* client) : StreamBusInterface(client)
+{
+ pasynCommon = NULL;
+ pasynOctet = NULL;
+ intrPvtOctet = NULL;
+ pasynInt32 = NULL;
+ intrPvtInt32 = NULL;
+ pasynUInt32 = NULL;
+ intrPvtUInt32 = NULL;
+ pasynGpib = NULL;
+ eventMask = 0;
+ receivedEvent = 0;
+ peeksize = 1;
+ pasynUser = pasynManager->createAsynUser(handleRequest,
+ handleTimeout);
+ assert(pasynUser);
+ pasynUser->userPvt = this;
+#ifdef EPICS_3_14
+ timerQueue = &epicsTimerQueueActive::allocate(true);
+ assert(timerQueue);
+ timer = &timerQueue->createTimer();
+ assert(timer);
+#else
+ timer = wdCreate();
+ callbackSetCallback(expire, &timeoutCallback);
+ callbackSetUser(this, &timeoutCallback);
+#endif
+}
+
+AsynDriverInterface::
+~AsynDriverInterface()
+{
+ cancelTimer();
+
+ if (intrPvtInt32)
+ {
+ // Int32 event interface is connected
+ pasynInt32->cancelInterruptUser(pvtInt32,
+ pasynUser, intrPvtInt32);
+ }
+ if (intrPvtUInt32)
+ {
+ // UInt32 event interface is connected
+ pasynUInt32->cancelInterruptUser(pvtUInt32,
+ pasynUser, intrPvtUInt32);
+ }
+ if (pasynOctet)
+ {
+ // octet stream interface is connected
+ int wasQueued;
+ if (intrPvtOctet)
+ {
+ pasynOctet->cancelInterruptUser(pvtOctet,
+ pasynUser, intrPvtOctet);
+ }
+ pasynManager->cancelRequest(pasynUser, &wasQueued);
+ // does not return until running handler has finished
+ }
+ // Now, no handler is running any more and none will start.
+
+#ifdef EPICS_3_14
+ timer->destroy();
+ timerQueue->release();
+#else
+ wdDelete(timer);
+#endif
+ pasynManager->disconnect(pasynUser);
+ pasynManager->freeAsynUser(pasynUser);
+ pasynUser = NULL;
+}
+
+// interface function getBusInterface():
+// do we have this bus/addr ?
+StreamBusInterface* AsynDriverInterface::
+getBusInterface(Client* client,
+ const char* busname, int addr, const char*)
+{
+ AsynDriverInterface* interface = new AsynDriverInterface(client);
+ if (interface->connectToBus(busname, addr))
+ {
+ debug ("AsynDriverInterface::getBusInterface(%s, %d): "
+ "new Interface allocated\n",
+ busname, addr);
+ return interface;
+ }
+ delete interface;
+ return NULL;
+}
+
+// interface function supportsEvent():
+// can we handle the StreamDevice command 'event'?
+bool AsynDriverInterface::
+supportsEvent()
+{
+ return (pasynInt32 != NULL) || (pasynUInt32 != NULL);
+}
+
+bool AsynDriverInterface::
+supportsAsyncRead()
+{
+ if (intrPvtOctet) return true;
+
+ // hook "I/O Intr" support
+ if (pasynOctet->registerInterruptUser(pvtOctet, pasynUser,
+ intrCallbackOctet, this, &intrPvtOctet) != asynSuccess)
+ {
+ error("%s: bus does not support asynchronous input: %s\n",
+ clientName(), pasynUser->errorMessage);
+ return false;
+ }
+ return true;
+}
+
+bool AsynDriverInterface::
+connectToBus(const char* busname, int addr)
+{
+ if (pasynManager->connectDevice(pasynUser, busname, addr) !=
+ asynSuccess)
+ {
+ // asynDriver does not know this busname/address
+ return false;
+ }
+
+ asynInterface* pasynInterface;
+
+ // find the asynCommon interface
+ pasynInterface = pasynManager->findInterface(pasynUser,
+ asynCommonType, true);
+ if(!pasynInterface)
+ {
+ error("%s: bus %s does not support asynCommon interface\n",
+ clientName(), busname);
+ return false;
+ }
+ pasynCommon = static_cast(pasynInterface->pinterface);
+ pvtCommon = pasynInterface->drvPvt;
+
+ // find the asynOctet interface
+ pasynInterface = pasynManager->findInterface(pasynUser,
+ asynOctetType, true);
+ if(!pasynInterface)
+ {
+ error("%s: bus %s does not support asynOctet interface\n",
+ clientName(), busname);
+ return false;
+ }
+ pasynOctet = static_cast(pasynInterface->pinterface);
+ pvtOctet = pasynInterface->drvPvt;
+
+ // is it a GPIB interface ?
+ pasynInterface = pasynManager->findInterface(pasynUser,
+ asynGpibType, true);
+ if(pasynInterface)
+ {
+ pasynGpib = static_cast(pasynInterface->pinterface);
+ pvtGpib = pasynInterface->drvPvt;
+ // asynGpib returns overflow error if we try to peek
+ // (read only one byte first).
+ peeksize = inputBuffer.capacity();
+ }
+
+ // look for interfaces for events
+ pasynInterface = pasynManager->findInterface(pasynUser,
+ asynInt32Type, true);
+ if(pasynInterface)
+ {
+ pasynInt32 = static_cast(pasynInterface->pinterface);
+ pvtInt32 = pasynInterface->drvPvt;
+ pasynUser->reason = ASYN_REASON_SIGNAL; // required for GPIB
+ if (pasynInt32->registerInterruptUser(pvtInt32, pasynUser,
+ intrCallbackInt32, this, &intrPvtInt32) == asynSuccess)
+ {
+ return true;
+ }
+ error("%s: bus %s does not allow to register for "
+ "Int32 interrupts: %s\n",
+ clientName(), busname, pasynUser->errorMessage);
+ pasynInt32 = NULL;
+ intrPvtInt32 = NULL;
+ }
+
+ // no asynInt32 available, thus try asynUInt32
+ pasynInterface = pasynManager->findInterface(pasynUser,
+ asynUInt32DigitalType, true);
+ if(pasynInterface)
+ {
+ pasynUInt32 =
+ static_cast(pasynInterface->pinterface);
+ pvtUInt32 = pasynInterface->drvPvt;
+ pasynUser->reason = ASYN_REASON_SIGNAL;
+ if (pasynUInt32->registerInterruptUser(pvtUInt32,
+ pasynUser, intrCallbackUInt32, this, 0xFFFFFFFF,
+ &intrPvtUInt32) == asynSuccess)
+ {
+ return true;
+ }
+ error("%s: bus %s does not allow to register for "
+ "UInt32 interrupts: %s\n",
+ clientName(), busname, pasynUser->errorMessage);
+ pasynUInt32 = NULL;
+ intrPvtUInt32 = NULL;
+ }
+
+ // no event interface available, never mind
+ return true;
+}
+
+// interface function: we want exclusive access to the device
+// lockTimeout_ms=0 means "block here" (used in @init)
+bool AsynDriverInterface::
+lockRequest(unsigned long lockTimeout_ms)
+{
+ debug("AsynDriverInterface::lockRequest(%s, %ld msec)\n",
+ clientName(), lockTimeout_ms);
+ asynStatus status;
+ lockTimeout = lockTimeout_ms ? lockTimeout_ms*0.001 : -1.0;
+ if (!lockTimeout_ms)
+ {
+ if (!connectToAsynPort()) return false;
+ }
+
+ ioAction = Lock;
+ status = pasynManager->queueRequest(pasynUser, priority(),
+ lockTimeout);
+ if (status != asynSuccess)
+ {
+ error("%s lockRequest: pasynManager->queueRequest() failed: %s\n",
+ clientName(), pasynUser->errorMessage);
+ return false;
+ }
+ // continues with:
+ // handleRequest() -> lockHandler() -> lockCallback()
+ // or handleTimeout() -> lockCallback(StreamIoTimeout)
+ return true;
+}
+
+bool AsynDriverInterface::
+connectToAsynPort()
+{
+ asynStatus status;
+ int connected;
+
+ debug("AsynDriverInterface::connectToAsynPort(%s)\n",
+ clientName());
+ status = pasynManager->isConnected(pasynUser, &connected);
+ if (status != asynSuccess)
+ {
+ error("%s: pasynManager->isConnected() failed: %s\n",
+ clientName(), pasynUser->errorMessage);
+ return false;
+ }
+ if (!connected)
+ {
+ status = pasynCommon->connect(pvtCommon, pasynUser);
+ debug("AsynDriverInterface::connectToAsynPort(%s): "
+ "status=%s\n",
+ clientName(), asynStatusStr[status]);
+ if (status != asynSuccess)
+ {
+ error("%s: pasynCommon->connect() failed: %s\n",
+ clientName(), pasynUser->errorMessage);
+ return false;
+ }
+ }
+ return true;
+}
+
+// now, we can have exclusive access (called by asynManager)
+void AsynDriverInterface::
+lockHandler()
+{
+ debug("AsynDriverInterface::lockHandler(%s)\n",
+ clientName());
+ pasynManager->blockProcessCallback(pasynUser, false);
+ lockCallback(StreamIoSuccess);
+}
+
+// interface function: we don't need exclusive access any more
+bool AsynDriverInterface::
+unlock()
+{
+ debug("AsynDriverInterface::unlock(%s)\n",
+ clientName());
+ pasynManager->unblockProcessCallback(pasynUser, false);
+ return true;
+}
+
+// interface function: we want to write something
+bool AsynDriverInterface::
+writeRequest(const void* output, size_t size,
+ unsigned long writeTimeout_ms)
+{
+#ifndef NO_TEMPORARY
+ debug("AsynDriverInterface::writeRequest(%s, \"%s\", %ld msec)\n",
+ clientName(), StreamBuffer(output, size).expand()(),
+ writeTimeout_ms);
+#endif
+
+ asynStatus status;
+ outputBuffer = (char*)output;
+ outputSize = size;
+ writeTimeout = writeTimeout_ms*0.001;
+ ioAction = Write;
+ status = pasynManager->queueRequest(pasynUser, priority(),
+ writeTimeout);
+ if (status != asynSuccess)
+ {
+ error("%s writeRequest: pasynManager->queueRequest() failed: %s\n",
+ clientName(), pasynUser->errorMessage);
+ return false;
+ }
+ // continues with:
+ // handleRequest() -> writeHandler() -> lockCallback()
+ // or handleTimeout() -> writeCallback(StreamIoTimeout)
+ return true;
+}
+
+// now, we can write (called by asynManager)
+void AsynDriverInterface::
+writeHandler()
+{
+ debug("AsynDriverInterface::writeHandler(%s)\n",
+ clientName());
+ asynStatus status;
+ size_t written = 0;
+ pasynUser->timeout = writeTimeout;
+
+ // discard any early input or early events
+ status = pasynOctet->flush(pvtOctet, pasynUser);
+ receivedEvent = 0;
+
+ if (status != asynSuccess)
+ {
+ error("%s: pasynOctet->flush() failed: %s\n",
+ clientName(), pasynUser->errorMessage);
+ writeCallback(StreamIoFault);
+ return;
+ }
+
+ // 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
+ {
+ status = pasynOctet->writeRaw(pvtOctet, pasynUser,
+ outputBuffer, outputSize, &written);
+ }
+ else // asyn should add eos
+ {
+ status = pasynOctet->write(pvtOctet, pasynUser,
+ outputBuffer, outputSize, &written);
+ }
+ switch (status)
+ {
+ case asynSuccess:
+ outputBuffer += written;
+ outputSize -= written;
+ if (outputSize > 0)
+ {
+ status = pasynManager->queueRequest(pasynUser,
+ priority(), lockTimeout);
+ if (status != asynSuccess)
+ {
+ error("%s writeHandler: "
+ "pasynManager->queueRequest() failed: %s\n",
+ clientName(), pasynUser->errorMessage);
+ writeCallback(StreamIoFault);
+ }
+ // continues with:
+ // handleRequest() -> writeHandler() -> writeCallback()
+ // or handleTimeout() -> writeCallback(StreamIoTimeout)
+ return;
+ }
+ writeCallback(StreamIoSuccess);
+ return;
+ case asynTimeout:
+ writeCallback(StreamIoTimeout);
+ return;
+ case asynOverflow:
+ error("%s: asynOverflow in write: %s\n",
+ clientName(), pasynUser->errorMessage);
+ writeCallback(StreamIoFault);
+ return;
+ case asynError:
+ error("%s: asynError in write: %s\n",
+ clientName(), pasynUser->errorMessage);
+ writeCallback(StreamIoFault);
+ return;
+ }
+}
+
+// interface function: we want to read something
+bool AsynDriverInterface::
+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",
+ clientName(), replyTimeout_ms, readTimeout_ms,
+ _expectedLength, async?"yes":"no");
+
+ asynStatus status;
+ readTimeout = readTimeout_ms*0.001;
+ replyTimeout = replyTimeout_ms*0.001;
+ double queueTimeout;
+ expectedLength = _expectedLength;
+
+ if (async)
+ {
+ ioAction = AsyncRead;
+ queueTimeout = -1.0;
+ // First poll for input (no timeout),
+ // later poll periodically if no other input arrives
+ // from intrCallbackOctet()
+ }
+ else {
+ ioAction = Read;
+ queueTimeout = replyTimeout;
+ }
+ status = pasynManager->queueRequest(pasynUser,
+ priority(), queueTimeout);
+ if (status != asynSuccess && !async)
+ {
+ error("%s readRequest: pasynManager->queueRequest() failed: %s\n",
+ clientName(), pasynUser->errorMessage);
+ return false;
+ }
+ // continues with:
+ // handleRequest() -> readHandler() -> readCallback()
+ // or handleTimeout() -> readCallback(StreamIoTimeout)
+ return true;
+}
+
+// now, we can read (called by asynManager)
+void AsynDriverInterface::
+readHandler()
+{
+ size_t streameoslen, deveoslen;
+ const char* streameos;
+ const char* deveos;
+ int oldeoslen = -1;
+ char oldeos[16];
+
+ // Setup eos if required.
+ streameos = getInTerminator(streameoslen);
+ deveos = streameos;
+ deveoslen = streameoslen;
+ if (streameos) do // 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)
+ {
+ 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);
+
+ int bytesToRead = peeksize;
+ long buffersize;
+
+ if (expectedLength > 0)
+ {
+ buffersize = expectedLength;
+ if (peeksize > 1)
+ {
+ /* we can't peek, try to read whole message */
+ bytesToRead = expectedLength;
+ }
+ }
+ else
+ {
+ buffersize = inputBuffer.capacity();
+ }
+ char* buffer = inputBuffer.clear().reserve(buffersize);
+
+ if (ioAction == AsyncRead)
+ {
+ // In AsyncRead mode just poll
+ // and read as much as possible
+ pasynUser->timeout = readTimeout;
+ bytesToRead = buffersize;
+ }
+ else
+ {
+ pasynUser->timeout = replyTimeout;
+ }
+ bool waitForReply = true;
+ int received;
+ int eomReason;
+ asynStatus status;
+ long readMore;
+
+ while (1)
+ {
+ readMore = 0;
+ received = 0;
+ eomReason = 0;
+
+ status = pasynOctet->read(pvtOctet, pasynUser,
+ buffer, bytesToRead, (size_t*)&received, &eomReason);
+ if (ioAction != AsyncRead || status != asynTimeout)
+ {
+ debug("AsynDriverInterface::readHandler(%s): "
+ "read(..., bytesToRead=%d, ...) [timeout=%f seconds] = %s\n",
+ clientName(), bytesToRead, pasynUser->timeout,
+ asynStatusStr[status]);
+ }
+ // pasynOctet->read() has already cut off terminator.
+
+ switch (status)
+ {
+ case asynSuccess:
+ if (ioAction == AsyncRead)
+ {
+#ifndef NO_TEMPORARY
+ debug("AsynDriverInterface::readHandler(%s): "
+ "AsyncRead poll: received %d of %d bytes \"%s\" "
+ "eomReason=%s [data ignored]\n",
+ clientName(), 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\" "
+ "eomReason=%s\n",
+ clientName(), received, bytesToRead,
+ StreamBuffer(buffer, received).expand()(),
+ eomReasonStr[eomReason&0x7]);
+#endif
+ // asynOctet->read() cuts off terminator, but:
+ // If stream has set a terminator which is longer
+ // than what the device (e.g. GPIB) can handle,
+ // only the last part is cut. If that part matches
+ // but the whole terminator does not, it is falsely cut.
+ // So what to do?
+ // Restore complete terminator and leave it to StreamCore to
+ // find out if this was really the end of the input.
+ // Warning: received can be < 0 if message was read in parts
+ // and a multi-byte terminator was partially read with last
+ // call.
+
+ if (deveoslen < streameoslen && (eomReason & ASYN_EOM_EOS))
+ {
+ size_t i;
+ for (i = 0; i < deveoslen; i++, received++)
+ {
+ if (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.
+ // Just take care of received < 0
+ }
+ eomReason &= ~ASYN_EOM_EOS;
+ }
+
+ readMore = readCallback(
+ eomReason & (ASYN_EOM_END|ASYN_EOM_EOS) ?
+ StreamIoEnd : StreamIoSuccess,
+ buffer, received);
+ break;
+ case asynTimeout:
+ if (received == 0 && waitForReply)
+ {
+ // reply timeout
+ if (ioAction == AsyncRead)
+ {
+ // start next poll after timer expires
+ if (replyTimeout != 0.0) startTimer(replyTimeout);
+ // continues with:
+ // timerExpired() -> queueRequest() -> handleRequest() -> readHandler()
+ // or intrCallbackOctet() -> asynReadHandler()
+ break;
+ }
+ debug("AsynDriverInterface::readHandler(%s): "
+ "no reply\n",
+ clientName());
+ readMore = readCallback(StreamIoNoReply);
+ break;
+ }
+ // read timeout
+#ifndef NO_TEMPORARY
+ debug("AsynDriverInterface::readHandler(%s): "
+ "ioAction=%s, timeout after %d of %d bytes \"%s\"\n",
+ clientName(), ioActionStr[ioAction],
+ received, bytesToRead,
+ StreamBuffer(buffer, received).expand()());
+#endif
+ if (ioAction == AsyncRead || ioAction == AsyncReadMore)
+ {
+ // we already got the data from asynReadHandler()
+ readCallback(StreamIoTimeout, NULL, 0);
+ break;
+ }
+ readMore = readCallback(StreamIoTimeout, buffer, received);
+ break;
+ case asynOverflow:
+ if (bytesToRead == 1)
+ {
+ // device does not support peeking
+ // try to read whole message next time
+ inputBuffer.clear().reserve(100);
+ } else {
+ // buffer was still too small
+ // try larger buffer next time
+ inputBuffer.clear().reserve(inputBuffer.capacity()*2);
+ }
+ peeksize = inputBuffer.capacity();
+ // deliver whatever we could save
+ error("%s: asynOverflow in read: %s\n",
+ clientName(), pasynUser->errorMessage);
+ readCallback(StreamIoFault, buffer, received);
+ break;
+ case asynError:
+ error("%s: asynError in read: %s\n",
+ clientName(), pasynUser->errorMessage);
+ readCallback(StreamIoFault, buffer, received);
+ break;
+ }
+ if (!readMore) break;
+ if (readMore > 0)
+ {
+ bytesToRead = readMore;
+ }
+ else
+ {
+ bytesToRead = inputBuffer.capacity();
+ }
+ debug("AsynDriverInterface::readHandler(%s) "
+ "readMore=%ld bytesToRead=%d\n",
+ clientName(), readMore, bytesToRead);
+ pasynUser->timeout = readTimeout;
+ waitForReply = false;
+ }
+
+ // restore original EOS
+ if (oldeoslen >= 0)
+ {
+ pasynOctet->setInputEos(pvtOctet, pasynUser,
+ oldeos, oldeoslen);
+ }
+}
+
+void intrCallbackOctet(void* /*pvt*/, asynUser *pasynUser,
+ char *data, size_t numchars, int eomReason)
+{
+ AsynDriverInterface* interface =
+ static_cast(pasynUser->userPvt);
+
+// 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
+// internal buffer of asynDriver.
+
+ 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
+ }
+}
+
+// get asynchronous input
+void AsynDriverInterface::
+asynReadHandler(const char *buffer, int received, int eomReason)
+{
+ // Due to multithreading, timerExpired() might come at any time.
+ // It queues the next poll request which is now useless because
+ // we got asynchronous input.
+ // Unfortunately, we must be very careful not to block in this
+ // function. That means, we must not call cancelRequest() from here!
+ // We must also not call cancelRequest() in any other method that
+ // might have been called in port thread context, like finish().
+ // Luckily, that request cannot be active right now, because we are
+ // inside the read handler of some other asynUser in the port thread.
+ // Thus, it is sufficient to mark the request as obsolete by
+ // setting ioAction=None. See handleRequest().
+
+#ifndef NO_TEMPORARY
+ debug("AsynDriverInterface::asynReadHandler(%s, buffer=\"%s\", "
+ "received=%d eomReason=%s) ioAction=%s\n",
+ clientName(), StreamBuffer(buffer, received).expand()(),
+ received, eomReasonStr[eomReason&0x7], ioActionStr[ioAction]);
+#endif
+
+ ioAction = None;
+ long readMore = 1;
+ if (received)
+ {
+ // At the moment, it seems that asynDriver does not cut off
+ // terminators for interrupt users and never sets eomReason.
+ // This may change in future releases of asynDriver.
+
+ asynStatus status;
+ const char* streameos;
+ size_t streameoslen;
+ streameos = getInTerminator(streameoslen);
+ char deveos[16]; // I guess that is sufficient
+ int deveoslen;
+
+ if (eomReason & ASYN_EOM_EOS)
+ {
+ // Terminator was cut off.
+ // If terminator is handled by stream, then we must
+ // restore the terminator, because the "real" terminator
+ // might be longer than what the octet driver supports.
+
+ if (!streameos) // stream has not defined eos
+ {
+ // Just handle eos as normal end condition
+ // and don't try to add it.
+ eomReason |= ASYN_EOM_END;
+ }
+ else
+ {
+ // Try to add terminator
+ status = pasynOctet->getInputEos(pvtOctet,
+ pasynUser, deveos, sizeof(deveos)-1, &deveoslen);
+ if (status == asynSuccess)
+ {
+ // We can't just append terminator to buffer, because
+ // we don't own that piece of memory.
+ // First process received data with cut-off terminator
+ readCallback(
+ StreamIoSuccess,
+ buffer, received);
+ // then add terminator
+ buffer = deveos;
+ received = deveoslen;
+ }
+ }
+ }
+ else if (!streameos)
+ {
+ // If terminator was not cut off and terminator was not
+ // set by stream, cut it off now.
+ status = pasynOctet->getInputEos(pvtOctet,
+ pasynUser, deveos, sizeof(deveos)-1, &deveoslen);
+ if (status == asynSuccess && received >= deveoslen)
+ {
+ int i;
+ for (i = 1; i <= deveoslen; i++)
+ {
+ if (buffer[received - i] != deveos[deveoslen - i]) break;
+ }
+ if (i > deveoslen) // terminator found
+ {
+ received -= deveoslen;
+ eomReason |= ASYN_EOM_END;
+ }
+ }
+ }
+ readMore = readCallback(
+ eomReason & ASYN_EOM_END ?
+ StreamIoEnd : StreamIoSuccess,
+ buffer, received);
+ }
+ if (readMore)
+ {
+ // wait for more input
+ ioAction = AsyncReadMore;
+ startTimer(readTimeout);
+ }
+ debug("AsynDriverInterface::asynReadHandler(%s) readMore=%li, ioAction=%s \n",
+ clientName(), readMore, ioActionStr[ioAction]);
+}
+
+// interface function: we want to receive an event
+bool AsynDriverInterface::
+acceptEvent(unsigned long mask, unsigned long replytimeout_ms)
+{
+ if (receivedEvent & mask)
+ {
+ // handle early events
+ receivedEvent = 0;
+ eventCallback(StreamIoSuccess);
+ return true;
+ }
+ eventMask = mask;
+ ioAction = ReceiveEvent;
+ startTimer(replytimeout_ms*0.001);
+ return true;
+}
+
+void intrCallbackInt32(void* /*pvt*/, asynUser *pasynUser, epicsInt32 data)
+{
+ AsynDriverInterface* interface =
+ static_cast(pasynUser->userPvt);
+ debug("AsynDriverInterface::intrCallbackInt32 (%s, %ld)\n",
+ interface->clientName(), (long int) data);
+ if (interface->eventMask)
+ {
+ if (data & interface->eventMask)
+ {
+ interface->eventMask = 0;
+ interface->eventCallback(StreamIoSuccess);
+ }
+ return;
+ }
+ // store early events
+ interface->receivedEvent = data;
+}
+
+void intrCallbackUInt32(void* /*pvt*/, asynUser *pasynUser,
+ epicsUInt32 data)
+{
+ AsynDriverInterface* interface =
+ static_cast(pasynUser->userPvt);
+ debug("AsynDriverInterface::intrCallbackUInt32 (%s, %ld)\n",
+ interface->clientName(), (long int) data);
+ if (interface->eventMask)
+ {
+ if (data & interface->eventMask)
+ {
+ interface->eventMask = 0;
+ interface->eventCallback(StreamIoSuccess);
+ }
+ return;
+ }
+ // store early events
+ interface->receivedEvent = data;
+}
+
+void AsynDriverInterface::
+timerExpired()
+{
+ int autoconnect, connected;
+ switch (ioAction)
+ {
+ case ReceiveEvent:
+ // timeout while waiting for event
+ ioAction = None;
+ eventCallback(StreamIoTimeout);
+ return;
+ case AsyncReadMore:
+ // timeout after reading some async data
+ // Deliver what we have.
+ readCallback(StreamIoTimeout);
+ // readCallback() may have started a new poll
+ return;
+ case AsyncRead:
+ // No async input for some time, thus let's poll.
+ // Due to multithreading, asynReadHandler() might be active
+ // at the moment if another asynUser got input right now.
+ // queueRequest might fail if another request was just queued
+ pasynManager->isAutoConnect(pasynUser, &autoconnect);
+ pasynManager->isConnected(pasynUser, &connected);
+ if (autoconnect && !connected)
+ {
+ // has explicitely been disconnected
+ // a poll would autoConnect which is not what we want
+ // just retry later
+ startTimer(replyTimeout);
+ }
+ else
+ {
+ // queue for read poll (no timeout)
+ pasynManager->queueRequest(pasynUser,
+ asynQueuePriorityLow, -1.0);
+ // continues with:
+ // handleRequest() -> readHandler() -> readCallback()
+ }
+ return;
+ default:
+ error("INTERNAL ERROR (%s): timerExpired() unexpected ioAction %s\n",
+ clientName(), ioActionStr[ioAction]);
+ return;
+ }
+}
+
+#ifdef EPICS_3_14
+epicsTimerNotify::expireStatus AsynDriverInterface::
+expire(const epicsTime &)
+{
+ timerExpired();
+ return noRestart;
+}
+#else
+void AsynDriverInterface::
+expire(CALLBACK *pcallback)
+{
+ AsynDriverInterface* interface =
+ static_cast(pcallback->user);
+ interface->timerExpired();
+}
+#endif
+
+bool AsynDriverInterface::
+connectRequest(unsigned long connecttimeout_ms)
+{
+ double queueTimeout = connecttimeout_ms*0.001;
+ asynStatus status;
+ ioAction = Connect;
+ status = pasynManager->queueRequest(pasynUser,
+ asynQueuePriorityConnect, queueTimeout);
+ if (status != asynSuccess)
+ {
+ error("%s connectRequest: pasynManager->queueRequest() failed: %s\n",
+ clientName(), pasynUser->errorMessage);
+ return false;
+ }
+ // continues with:
+ // handleRequest() -> connectHandler() -> connectCallback()
+ // or handleTimeout() -> connectCallback(StreamIoTimeout)
+ return true;
+}
+
+void AsynDriverInterface::
+connectHandler()
+{
+ asynStatus status;
+ status = pasynCommon->connect(pvtCommon, pasynUser);
+ if (status != asynSuccess)
+ {
+ error("%s connectRequest: pasynCommon->connect() failed: %s\n",
+ clientName(), pasynUser->errorMessage);
+ connectCallback(StreamIoFault);
+ return;
+ }
+ connectCallback(StreamIoSuccess);
+}
+
+bool AsynDriverInterface::
+disconnect()
+{
+ asynStatus status;
+ ioAction = Disconnect;
+ status = pasynManager->queueRequest(pasynUser,
+ asynQueuePriorityConnect, 0.0);
+ if (status != asynSuccess)
+ {
+ error("%s disconnect: pasynManager->queueRequest() failed: %s\n",
+ clientName(), pasynUser->errorMessage);
+ return false;
+ }
+ // continues with:
+ // handleRequest() -> disconnectHandler()
+ // or handleTimeout()
+ // (does not expect callback)
+ return true;
+}
+
+void AsynDriverInterface::
+disconnectHandler()
+{
+ asynStatus status;
+ status = pasynCommon->disconnect(pvtCommon, pasynUser);
+ if (status != asynSuccess)
+ {
+ error("%s connectRequest: pasynCommon->disconnect() failed: %s\n",
+ clientName(), pasynUser->errorMessage);
+ return;
+ }
+}
+
+void AsynDriverInterface::
+finish()
+{
+ debug("AsynDriverInterface::finish(%s) start\n",
+ clientName());
+ cancelTimer();
+ ioAction = None;
+ debug("AsynDriverInterface::finish(%s) done\n",
+ clientName());
+}
+
+// asynUser callbacks to pasynManager->queueRequest()
+
+void handleRequest(asynUser* pasynUser)
+{
+ AsynDriverInterface* interface =
+ static_cast(pasynUser->userPvt);
+ switch (interface->ioAction)
+ {
+ case None:
+ // ignore obsolete poll request
+ // see asynReadHandler()
+ break;
+ case Lock:
+ interface->lockHandler();
+ break;
+ case Write:
+ interface->writeHandler();
+ break;
+ case AsyncRead: // polled async input
+ case AsyncReadMore:
+ case Read: // sync input
+ interface->readHandler();
+ break;
+ case Connect:
+ interface->connectHandler();
+ break;
+ case Disconnect:
+ interface->disconnectHandler();
+ break;
+ default:
+ error("INTERNAL ERROR (%s): "
+ "handleRequest() unexpected ioAction %s\n",
+ interface->clientName(), ioActionStr[interface->ioAction]);
+ }
+}
+
+void handleTimeout(asynUser* pasynUser)
+{
+ AsynDriverInterface* interface =
+ static_cast(pasynUser->userPvt);
+ switch (interface->ioAction)
+ {
+ case Lock:
+ interface->lockCallback(StreamIoTimeout);
+ break;
+ case Write:
+ interface->writeCallback(StreamIoTimeout);
+ break;
+ case Read:
+ interface->readCallback(StreamIoFault, NULL, 0);
+ break;
+ case AsyncReadMore:
+ interface->readCallback(StreamIoTimeout, NULL, 0);
+ break;
+ case Connect:
+ interface->connectCallback(StreamIoTimeout);
+ break;
+ case Disconnect:
+ // not interested in callback
+ break;
+ // No AsyncRead here because we don't use timeout when polling
+ default:
+ error("INTERNAL ERROR (%s): handleTimeout() "
+ "unexpected ioAction %s\n",
+ interface->clientName(), ioActionStr[interface->ioAction]);
+ }
+}
+
diff --git a/src/BCDConverter.cc b/src/BCDConverter.cc
new file mode 100644
index 0000000..1c51b8b
--- /dev/null
+++ b/src/BCDConverter.cc
@@ -0,0 +1,155 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is the BCD 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"
+
+// packed BCD (0x00 - 0x99)
+
+class BCDConverter : public StreamFormatConverter
+{
+ int parse (const StreamFormat&, StreamBuffer&, const char*&, bool);
+ bool printLong(const StreamFormat&, StreamBuffer&, long);
+ int scanLong(const StreamFormat&, const char*, long&);
+};
+
+int BCDConverter::
+parse(const StreamFormat&, StreamBuffer&, const char*&, bool)
+{
+ return long_format;
+}
+
+bool BCDConverter::
+printLong(const StreamFormat& fmt, StreamBuffer& output, long value)
+{
+ unsigned char bcd[6]={0,0,0,0,0,0}; // sufficient for 2^32
+ int i;
+ int prec = fmt.prec; // number of nibbles
+ if (prec == -1)
+ {
+ prec = 2 * sizeof (value);
+ }
+ int width = (prec + (fmt.flags & sign_flag ? 2 : 1)) / 2;
+ if (fmt.width > width) width = fmt.width;
+ if (fmt.flags & sign_flag && value < 0)
+ {
+ // negative BCD value, I hope "F" as "-" is OK
+ bcd[5] = 0xF0;
+ value = -value;
+ }
+ if (prec > 10) prec = 10;
+ for (i = 0; i < prec; i++)
+ {
+ bcd[i/2] |= (value % 10) << (4 * (i & 1));
+ value /= 10;
+ }
+ if (fmt.flags & alt_flag)
+ {
+ // least significant byte first (little endian)
+ for (i = 0; i < (prec + 1) / 2; i++)
+ {
+ output.append(bcd[i]);
+ }
+ for (; i < width; i++)
+ {
+ output.append('\0');
+ }
+ output[-1] |= bcd[5];
+ }
+ else
+ {
+ // most significant byte first (big endian)
+ int firstbyte = output.length();
+ for (i = 0; i < width - (prec + 1) / 2; i++)
+ {
+ output.append('\0');
+ }
+ for (i = (prec - 1) / 2; i >= 0; i--)
+ {
+ output.append(bcd[i]);
+ }
+ output[firstbyte] |= bcd[5];
+ }
+ return true;
+}
+
+int BCDConverter::
+scanLong(const StreamFormat& fmt, const char* input, long& value)
+{
+ int length = 0;
+ int val = 0;
+ unsigned char bcd1, bcd10;
+ int width = fmt.width;
+ if (width == 0) width = 1;
+ if (fmt.flags & alt_flag)
+ {
+ // little endian
+ int shift = 1;
+ while (width--)
+ {
+ bcd1 = bcd10 = (unsigned char) input[length++];
+ bcd1 &= 0x0F;
+ bcd10 >>= 4;
+ if (bcd1 > 9 || shift * bcd1 < bcd1) break;
+ if (width == 0 && fmt.flags & sign_flag)
+ {
+ val += bcd1 * shift;
+ if (bcd10 != 0) val = -val;
+ break;
+ }
+ if (bcd10 > 9 || shift * bcd10 < bcd10) break;
+ val += (bcd1 + 10 * bcd10) * shift;
+ if (shift <= 100000000) shift *= 100;
+ else shift = 0;
+ }
+ }
+ else
+ {
+ // big endian
+ int sign = 1;
+ while (width--)
+ {
+ long temp;
+ bcd1 = bcd10 = (unsigned char) input[length];
+ bcd1 &= 0x0F;
+ bcd10 >>= 4;
+ if (length == 0 && fmt.flags & sign_flag && bcd10)
+ {
+ sign = -1;
+ bcd10 = 0;
+ }
+ if (bcd1 > 9 || bcd10 > 9) break;
+ temp = val * 100 + (bcd1 + 10 * bcd10);
+ if (temp < val)
+ {
+ length = 0;
+ break;
+ }
+ val = temp;
+ length++;
+ }
+ val *= sign;
+ }
+ if (length == 0) return -1;
+ value = val;
+ return length;
+}
+
+RegisterConverter (BCDConverter, "D");
diff --git a/src/BinaryConverter.cc b/src/BinaryConverter.cc
new file mode 100644
index 0000000..cbe9449
--- /dev/null
+++ b/src/BinaryConverter.cc
@@ -0,0 +1,118 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is the binary 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
+#include "StreamFormatConverter.h"
+#include "StreamError.h"
+
+// Binary ASCII Converter %b and %B
+
+class BinaryConverter : public StreamFormatConverter
+{
+ int parse(const StreamFormat&, StreamBuffer&, const char*&, bool);
+ bool printLong(const StreamFormat&, StreamBuffer&, long);
+ int scanLong(const StreamFormat&, const char*, long&);
+};
+
+int BinaryConverter::
+parse(const StreamFormat& format, StreamBuffer& info,
+ const char*& source, bool)
+{
+ if (format.conv == 'B')
+ {
+ // user defined characters for %B (next 2 in source)
+ if (!*source )
+ {
+ if (*source == esc) source++;
+ info.append(*source++);
+ if (!*source)
+ {
+ if (*source == esc) source++;
+ info.append(*source++);
+ return long_format;
+ }
+ }
+ error("Missing characters after %%B format conversion\n");
+ return false;
+ }
+ // default characters for %b
+ info.append("01");
+ return long_format;
+}
+
+bool BinaryConverter::
+printLong(const StreamFormat& format, StreamBuffer& output, long value)
+{
+ int prec = format.prec;
+ if (prec == -1)
+ {
+ // find number of significant bits
+ prec = sizeof (value) * 8;
+ while (prec && (value & (1 << (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))
+ {
+ // pad left
+ char fill = (format.flags & zero_flag) ? zero : ' ';
+ while (width > prec)
+ {
+ output.append(fill);
+ width--;
+ }
+ }
+ while (prec--)
+ {
+ output.append((value & (1 << prec)) ? one : zero);
+ width--;
+ }
+ while (width--)
+ {
+ // pad right
+ output.append(' ');
+ }
+ return true;
+}
+
+int BinaryConverter::
+scanLong(const StreamFormat& format, const char* input, long& value)
+{
+ long val = 0;
+ 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 (input[length] != zero && input[length] != one) return -1;
+ while (width-- && (input[length] == zero || input[length] == one))
+ {
+ val <<= 1;
+ if (input[length++] == one) val |= 1;
+ }
+ value = val;
+ return length;
+}
+
+RegisterConverter (BinaryConverter, "bB");
diff --git a/src/CONFIG_STREAM b/src/CONFIG_STREAM
new file mode 100644
index 0000000..d487bd9
--- /dev/null
+++ b/src/CONFIG_STREAM
@@ -0,0 +1,65 @@
+# Want debugging?
+# HOST_OPT = NO
+
+# You may add more record interfaces
+# This requires the naming conventions
+# dev$(RECORD)Stream.c
+
+RECORDS += ao ai
+RECORDS += bo bi
+RECORDS += mbbo mbbi
+RECORDS += mbboDirect mbbiDirect
+RECORDS += longout longin
+RECORDS += stringout stringin
+RECORDS += waveform
+RECORDS += calcout
+#RECORDS += aai aao
+
+# Do you have synApps and want support for scalcout?
+# Then define CALC or SYNAPPS in your RELEASE file
+# pointing to the 'calc' module of synApps.
+# Due to strange cross dependencies in synApps
+# 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
+# This requires the naming convention
+# $(BUS)Interface.cc
+# asynDriver interface is added automatically
+# if ASYN is defined in your RELEASE file.
+# BUSSES += Debug
+
+# You may add more format converters
+# This requires the naming convention
+# $(FORMAT)Converter.cc
+
+FORMATS += Enum
+FORMATS += BCD
+FORMATS += Raw
+FORMATS += Binary
+FORMATS += Checksum
+
+# Want a loadable module?
+# For Tornado 2.0.2, a fix is needed in the
+# registerRecordDeviceDriver.pl script in base:
+# at the end replace the line "IoccrfReg iocshReg;"
+# 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
+
+STREAM_SRCS += StreamVersion.c
+STREAM_SRCS += StreamBuffer.cc
+STREAM_SRCS += StreamError.cc
+STREAM_SRCS += StreamProtocol.cc
+STREAM_SRCS += StreamFormatConverter.cc
+STREAM_SRCS += StreamCore.cc
+STREAM_SRCS += StreamBusInterface.cc
+STREAM_SRCS += StreamEpics.cc
diff --git a/src/ChecksumConverter.cc b/src/ChecksumConverter.cc
new file mode 100644
index 0000000..2a5ea72
--- /dev/null
+++ b/src/ChecksumConverter.cc
@@ -0,0 +1,645 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2006 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is the checksum pseudo-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"
+#if defined(__vxworks) || defined(vxWorks) || defined(_WIN32) || defined(__rtems__)
+// These systems have no strncasecmp
+#include
+#define strncasecmp epicsStrnCaseCmp
+#endif
+#include
+
+typedef unsigned long ulong;
+typedef unsigned char uchar;
+
+typedef ulong (*checksumFunc)(const uchar* data, ulong len, ulong init);
+
+static ulong sum(const uchar* data, ulong len, ulong sum)
+{
+ while (len--)
+ {
+ sum += *data++;
+ }
+ return sum;
+}
+
+static ulong xor8(const uchar* data, ulong len, ulong sum)
+{
+ while (len--)
+ {
+ sum ^= *data++;
+ }
+ return sum;
+}
+
+static ulong crc_0x07(const uchar* data, ulong len, ulong crc)
+{
+ // x^8 + x^2 + x^1 + x^0 (0x07)
+ const static uchar table[256] = {
+ 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
+ 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
+ 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
+ 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
+ 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5,
+ 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
+ 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85,
+ 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
+ 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
+ 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
+ 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2,
+ 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
+ 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32,
+ 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
+ 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
+ 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
+ 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C,
+ 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
+ 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC,
+ 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
+ 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
+ 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
+ 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C,
+ 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
+ 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B,
+ 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
+ 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
+ 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
+ 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB,
+ 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
+ 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB,
+ 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 };
+
+ while (len--) crc = table[(crc ^ *data++) & 0xFF];
+ return crc;
+}
+
+static ulong crc_0x31(const uchar* data, ulong len, ulong crc)
+{
+ // x^8 + x^5 + x^4 + x^0 (0x31)
+ const static uchar table[256] = {
+ 0x00, 0x5e, 0xbc, 0xe2, 0x61, 0x3f, 0xdd, 0x83,
+ 0xc2, 0x9c, 0x7e, 0x20, 0xa3, 0xfd, 0x1f, 0x41,
+ 0x9d, 0xc3, 0x21, 0x7f, 0xfc, 0xa2, 0x40, 0x1e,
+ 0x5f, 0x01, 0xe3, 0xbd, 0x3e, 0x60, 0x82, 0xdc,
+ 0x23, 0x7d, 0x9f, 0xc1, 0x42, 0x1c, 0xfe, 0xa0,
+ 0xe1, 0xbf, 0x5d, 0x03, 0x80, 0xde, 0x3c, 0x62,
+ 0xbe, 0xe0, 0x02, 0x5c, 0xdf, 0x81, 0x63, 0x3d,
+ 0x7c, 0x22, 0xc0, 0x9e, 0x1d, 0x43, 0xa1, 0xff,
+ 0x46, 0x18, 0xfa, 0xa4, 0x27, 0x79, 0x9b, 0xc5,
+ 0x84, 0xda, 0x38, 0x66, 0xe5, 0xbb, 0x59, 0x07,
+ 0xdb, 0x85, 0x67, 0x39, 0xba, 0xe4, 0x06, 0x58,
+ 0x19, 0x47, 0xa5, 0xfb, 0x78, 0x26, 0xc4, 0x9a,
+ 0x65, 0x3b, 0xd9, 0x87, 0x04, 0x5a, 0xb8, 0xe6,
+ 0xa7, 0xf9, 0x1b, 0x45, 0xc6, 0x98, 0x7a, 0x24,
+ 0xf8, 0xa6, 0x44, 0x1a, 0x99, 0xc7, 0x25, 0x7b,
+ 0x3a, 0x64, 0x86, 0xd8, 0x5b, 0x05, 0xe7, 0xb9,
+ 0x8c, 0xd2, 0x30, 0x6e, 0xed, 0xb3, 0x51, 0x0f,
+ 0x4e, 0x10, 0xf2, 0xac, 0x2f, 0x71, 0x93, 0xcd,
+ 0x11, 0x4f, 0xad, 0xf3, 0x70, 0x2e, 0xcc, 0x92,
+ 0xd3, 0x8d, 0x6f, 0x31, 0xb2, 0xec, 0x0e, 0x50,
+ 0xaf, 0xf1, 0x13, 0x4d, 0xce, 0x90, 0x72, 0x2c,
+ 0x6d, 0x33, 0xd1, 0x8f, 0x0c, 0x52, 0xb0, 0xee,
+ 0x32, 0x6c, 0x8e, 0xd0, 0x53, 0x0d, 0xef, 0xb1,
+ 0xf0, 0xae, 0x4c, 0x12, 0x91, 0xcf, 0x2d, 0x73,
+ 0xca, 0x94, 0x76, 0x28, 0xab, 0xf5, 0x17, 0x49,
+ 0x08, 0x56, 0xb4, 0xea, 0x69, 0x37, 0xd5, 0x8b,
+ 0x57, 0x09, 0xeb, 0xb5, 0x36, 0x68, 0x8a, 0xd4,
+ 0x95, 0xcb, 0x29, 0x77, 0xf4, 0xaa, 0x48, 0x16,
+ 0xe9, 0xb7, 0x55, 0x0b, 0x88, 0xd6, 0x34, 0x6a,
+ 0x2b, 0x75, 0x97, 0xc9, 0x4a, 0x14, 0xf6, 0xa8,
+ 0x74, 0x2a, 0xc8, 0x96, 0x15, 0x4b, 0xa9, 0xf7,
+ 0xb6, 0xe8, 0x0a, 0x54, 0xd7, 0x89, 0x6b, 0x35 };
+
+ while (len--) crc = table[(crc ^ *data++) & 0xFF];
+ return crc;
+}
+
+static ulong crc_0x8005(const uchar* data, ulong len, ulong crc)
+{
+ // x^16 + x^15 + x^2 + x^0 (0x8005)
+ const static unsigned short table[256] = {
+ 0x0000,0x8005,0x800f,0x000a,0x801b,0x001e,0x0014,0x8011,
+ 0x8033,0x0036,0x003c,0x8039,0x0028,0x802d,0x8027,0x0022,
+ 0x8063,0x0066,0x006c,0x8069,0x0078,0x807d,0x8077,0x0072,
+ 0x0050,0x8055,0x805f,0x005a,0x804b,0x004e,0x0044,0x8041,
+ 0x80c3,0x00c6,0x00cc,0x80c9,0x00d8,0x80dd,0x80d7,0x00d2,
+ 0x00f0,0x80f5,0x80ff,0x00fa,0x80eb,0x00ee,0x00e4,0x80e1,
+ 0x00a0,0x80a5,0x80af,0x00aa,0x80bb,0x00be,0x00b4,0x80b1,
+ 0x8093,0x0096,0x009c,0x8099,0x0088,0x808d,0x8087,0x0082,
+ 0x8183,0x0186,0x018c,0x8189,0x0198,0x819d,0x8197,0x0192,
+ 0x01b0,0x81b5,0x81bf,0x01ba,0x81ab,0x01ae,0x01a4,0x81a1,
+ 0x01e0,0x81e5,0x81ef,0x01ea,0x81fb,0x01fe,0x01f4,0x81f1,
+ 0x81d3,0x01d6,0x01dc,0x81d9,0x01c8,0x81cd,0x81c7,0x01c2,
+ 0x0140,0x8145,0x814f,0x014a,0x815b,0x015e,0x0154,0x8151,
+ 0x8173,0x0176,0x017c,0x8179,0x0168,0x816d,0x8167,0x0162,
+ 0x8123,0x0126,0x012c,0x8129,0x0138,0x813d,0x8137,0x0132,
+ 0x0110,0x8115,0x811f,0x011a,0x810b,0x010e,0x0104,0x8101,
+ 0x8303,0x0306,0x030c,0x8309,0x0318,0x831d,0x8317,0x0312,
+ 0x0330,0x8335,0x833f,0x033a,0x832b,0x032e,0x0324,0x8321,
+ 0x0360,0x8365,0x836f,0x036a,0x837b,0x037e,0x0374,0x8371,
+ 0x8353,0x0356,0x035c,0x8359,0x0348,0x834d,0x8347,0x0342,
+ 0x03c0,0x83c5,0x83cf,0x03ca,0x83db,0x03de,0x03d4,0x83d1,
+ 0x83f3,0x03f6,0x03fc,0x83f9,0x03e8,0x83ed,0x83e7,0x03e2,
+ 0x83a3,0x03a6,0x03ac,0x83a9,0x03b8,0x83bd,0x83b7,0x03b2,
+ 0x0390,0x8395,0x839f,0x039a,0x838b,0x038e,0x0384,0x8381,
+ 0x0280,0x8285,0x828f,0x028a,0x829b,0x029e,0x0294,0x8291,
+ 0x82b3,0x02b6,0x02bc,0x82b9,0x02a8,0x82ad,0x82a7,0x02a2,
+ 0x82e3,0x02e6,0x02ec,0x82e9,0x02f8,0x82fd,0x82f7,0x02f2,
+ 0x02d0,0x82d5,0x82df,0x02da,0x82cb,0x02ce,0x02c4,0x82c1,
+ 0x8243,0x0246,0x024c,0x8249,0x0258,0x825d,0x8257,0x0252,
+ 0x0270,0x8275,0x827f,0x027a,0x826b,0x026e,0x0264,0x8261,
+ 0x0220,0x8225,0x822f,0x022a,0x823b,0x023e,0x0234,0x8231,
+ 0x8213,0x0216,0x021c,0x8219,0x0208,0x820d,0x8207,0x0202 };
+
+ while (len--) crc = table[((crc>>8) ^ *data++) & 0xFF] ^ (crc << 8);
+ return crc;
+}
+
+static ulong crc_0x8005_r(const uchar* data, ulong len, ulong crc)
+{
+ // x^16 + x^15 + x^2 + x^0 (0x8005)
+ // reflected
+ const static unsigned short table[256] = {
+ 0x0000,0xC0C1,0xC181,0x0140,0xC301,0x03C0,0x0280,0xC241,
+ 0xC601,0x06C0,0x0780,0xC741,0x0500,0xC5C1,0xC481,0x0440,
+ 0xCC01,0x0CC0,0x0D80,0xCD41,0x0F00,0xCFC1,0xCE81,0x0E40,
+ 0x0A00,0xCAC1,0xCB81,0x0B40,0xC901,0x09C0,0x0880,0xC841,
+ 0xD801,0x18C0,0x1980,0xD941,0x1B00,0xDBC1,0xDA81,0x1A40,
+ 0x1E00,0xDEC1,0xDF81,0x1F40,0xDD01,0x1DC0,0x1C80,0xDC41,
+ 0x1400,0xD4C1,0xD581,0x1540,0xD701,0x17C0,0x1680,0xD641,
+ 0xD201,0x12C0,0x1380,0xD341,0x1100,0xD1C1,0xD081,0x1040,
+ 0xF001,0x30C0,0x3180,0xF141,0x3300,0xF3C1,0xF281,0x3240,
+ 0x3600,0xF6C1,0xF781,0x3740,0xF501,0x35C0,0x3480,0xF441,
+ 0x3C00,0xFCC1,0xFD81,0x3D40,0xFF01,0x3FC0,0x3E80,0xFE41,
+ 0xFA01,0x3AC0,0x3B80,0xFB41,0x3900,0xF9C1,0xF881,0x3840,
+ 0x2800,0xE8C1,0xE981,0x2940,0xEB01,0x2BC0,0x2A80,0xEA41,
+ 0xEE01,0x2EC0,0x2F80,0xEF41,0x2D00,0xEDC1,0xEC81,0x2C40,
+ 0xE401,0x24C0,0x2580,0xE541,0x2700,0xE7C1,0xE681,0x2640,
+ 0x2200,0xE2C1,0xE381,0x2340,0xE101,0x21C0,0x2080,0xE041,
+ 0xA001,0x60C0,0x6180,0xA141,0x6300,0xA3C1,0xA281,0x6240,
+ 0x6600,0xA6C1,0xA781,0x6740,0xA501,0x65C0,0x6480,0xA441,
+ 0x6C00,0xACC1,0xAD81,0x6D40,0xAF01,0x6FC0,0x6E80,0xAE41,
+ 0xAA01,0x6AC0,0x6B80,0xAB41,0x6900,0xA9C1,0xA881,0x6840,
+ 0x7800,0xB8C1,0xB981,0x7940,0xBB01,0x7BC0,0x7A80,0xBA41,
+ 0xBE01,0x7EC0,0x7F80,0xBF41,0x7D00,0xBDC1,0xBC81,0x7C40,
+ 0xB401,0x74C0,0x7580,0xB541,0x7700,0xB7C1,0xB681,0x7640,
+ 0x7200,0xB2C1,0xB381,0x7340,0xB101,0x71C0,0x7080,0xB041,
+ 0x5000,0x90C1,0x9181,0x5140,0x9301,0x53C0,0x5280,0x9241,
+ 0x9601,0x56C0,0x5780,0x9741,0x5500,0x95C1,0x9481,0x5440,
+ 0x9C01,0x5CC0,0x5D80,0x9D41,0x5F00,0x9FC1,0x9E81,0x5E40,
+ 0x5A00,0x9AC1,0x9B81,0x5B40,0x9901,0x59C0,0x5880,0x9841,
+ 0x8801,0x48C0,0x4980,0x8941,0x4B00,0x8BC1,0x8A81,0x4A40,
+ 0x4E00,0x8EC1,0x8F81,0x4F40,0x8D01,0x4DC0,0x4C80,0x8C41,
+ 0x4400,0x84C1,0x8581,0x4540,0x8701,0x47C0,0x4680,0x8641,
+ 0x8201,0x42C0,0x4380,0x8341,0x4100,0x81C1,0x8081,0x4040 };
+
+ while (len--) crc = table[(crc ^ *data++) & 0xFF] ^ (crc >> 8);
+ return crc;
+}
+
+static ulong crc_0x1021(const uchar* data, ulong len, ulong crc)
+{
+ // x^16 + x^12 + x^5 + x^0 (0x1021)
+ const static unsigned short table[256] = {
+ 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
+ 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
+ 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
+ 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
+ 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
+ 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
+ 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
+ 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
+ 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
+ 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
+ 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
+ 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
+ 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
+ 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
+ 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
+ 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
+ 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
+ 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
+ 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
+ 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
+ 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
+ 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
+ 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
+ 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
+ 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
+ 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
+ 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
+ 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
+ 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
+ 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
+ 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
+ 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 };
+
+ while (len--) crc = table[((crc>>8) ^ *data++) & 0xFF] ^ (crc << 8);
+ return crc;
+}
+
+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[] = {
+ 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
+ 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
+ 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
+ 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
+ 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
+ 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
+ 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
+ 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
+ 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
+ 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
+ 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
+ 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
+ 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
+ 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
+ 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
+ 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
+ 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
+ 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
+ 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
+ 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
+ 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
+ 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
+ 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
+ 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
+ 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
+ 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
+ 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
+ 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
+ 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
+ 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
+ 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
+ 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
+ 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
+ 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
+ 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
+ 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
+ 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
+ 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
+ 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
+ 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
+ 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
+ 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
+ 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
+ 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
+ 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
+ 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
+ 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
+ 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
+ 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
+ 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
+ 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
+ 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
+ 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
+ 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
+ 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
+ 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
+ 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
+ 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
+ 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
+ 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
+ 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
+ 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
+ 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
+ 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 };
+
+ while (len--) crc = table[((crc>>24) ^ *data++) & 0xFF] ^ (crc << 8);
+ return crc;
+}
+
+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[] = {
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
+ 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+ 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+ 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+ 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
+ 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
+ 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+ 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+ 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+ 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
+ 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
+ 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+ 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+ 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
+ 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
+ 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+ 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+ 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+ 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
+ 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
+ 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+ 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+ 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+ 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
+ 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
+ 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+ 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+ 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+ 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
+ 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
+ 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+ 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+ 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+ 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
+ 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
+ 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+ 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+ 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
+ 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
+ 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+ 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+ 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+ 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
+ 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
+ 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+ 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+ 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+ 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
+ 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
+ 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+ 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d };
+
+ while (len--) crc = table[(crc ^ *data++) & 0xFF] ^ (crc >> 8);
+ return crc;
+}
+
+static ulong adler32(const uchar* data, ulong len, ulong init)
+{
+ ulong a = init & 0xFFFF;
+ ulong b = (init >> 16) & 0xFFFF;
+
+ while (len) {
+ ulong tlen = len > 5550 ? 5550 : len;
+ len -= tlen;
+ do {
+ a += *data++;
+ b += a;
+ } while (--tlen);
+ a = (a & 0xFFFF) + (a >> 16) * 15;
+ b = (b & 0xFFFF) + (b >> 16) * 15;
+ }
+ if (a >= 65521) a -= 65521;
+ b = (b & 0xFFFF) + (b >> 16) * 15;
+ if (b >= 65521) b -= 65521;
+ return b << 16 | a;
+}
+
+static ulong hexsum(const uchar* data, ulong len, ulong sum)
+{
+ // Add all hex digits, ignore all other bytes.
+ ulong d;
+ while (len--)
+ {
+ d = toupper(*data++);
+ if (isxdigit(d))
+ {
+ if (isdigit(d)) d -= '0';
+ else d -= 'A' - 0x0A;
+ sum += d;
+ }
+ }
+ return sum;
+}
+
+struct checksum
+{
+ const char* name;
+ checksumFunc func;
+ ulong init;
+ ulong xorout;
+ signed char bytes;
+};
+
+static checksum checksumMap[] =
+// You may add your own checksum functions to this map.
+{
+// name func init xorout bytes chk("123456789")
+ {"sum", sum, 0x00, 0x00, 1}, // 0xDD
+ {"sum8", sum, 0x00, 0x00, 1}, // 0xDD
+ {"sum16", sum, 0x0000, 0x0000, 2}, // 0x01DD
+ {"sum32", sum, 0x00000000, 0x00000000, 4}, // 0x000001DD
+ {"nsum", sum, 0xff, 0xff, 1}, // 0x23
+ {"negsum", sum, 0xff, 0xff, 1}, // 0x23
+ {"-sum", sum, 0xff, 0xff, 1}, // 0x23
+ {"notsum", sum, 0x00, 0xff, 1}, // 0x22
+ {"~sum", sum, 0x00, 0xff, 1}, // 0x22
+ {"xor", xor8, 0x00, 0x00, 1}, // 0x31
+ {"crc8", crc_0x07, 0x00, 0x00, 1}, // 0xF4
+ {"ccitt8", crc_0x31, 0x00, 0x00, 1}, // 0xA1
+ {"crc16", crc_0x8005, 0x0000, 0x0000, 2}, // 0xFEE8
+ {"crc16r", crc_0x8005_r, 0x0000, 0x0000, 2}, // 0xBB3D
+ {"ccitt16", crc_0x1021, 0xFFFF, 0x0000, 2}, // 0x29B1
+ {"ccitt16a",crc_0x1021, 0x1D0F, 0x0000, 2}, // 0xE5CC
+ {"crc32", crc_0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF, 4}, // 0xFC891918
+ {"crc32r", crc_0x04C11DB7_r, 0xFFFFFFFF, 0xFFFFFFFF, 4}, // 0xCBF43926
+ {"jamcrc", crc_0x04C11DB7_r, 0xFFFFFFFF, 0x00000000, 4}, // 0x340BC6D9
+ {"adler32", adler32, 0x00000001, 0x00000000, 4}, // 0x091E01DE
+ {"hexsum8", hexsum, 0x00, 0x00, 1} // 0x2D
+};
+
+static ulong mask[5] = {0, 0xFF, 0xFFFF, 0xFFFFFF, 0xFFFFFFFF};
+
+class ChecksumConverter : public StreamFormatConverter
+{
+ int parse (const StreamFormat&, StreamBuffer&, const char*&, bool);
+ bool printPseudo(const StreamFormat&, StreamBuffer&);
+ int scanPseudo(const StreamFormat&, StreamBuffer&, long& cursor);
+};
+
+int ChecksumConverter::
+parse(const StreamFormat&, StreamBuffer& info, const char*& source, bool)
+{
+ const char* p = strchr(source, '>');
+ if (!p)
+ {
+ error ("Missing terminating '>' in checksum format.\n");
+ return false;
+ }
+
+ size_t fnum;
+ for (fnum = 0; fnum < sizeof(checksumMap)/sizeof(checksum); fnum++)
+ {
+ if (strncasecmp(source, checksumMap[fnum].name, p-source) == 0)
+ {
+ info.append(fnum);
+ source = p+1;
+ return pseudo_format;
+ }
+ }
+
+ error ("Unknown checksum algorithm \"%.*s\"\n",
+ static_cast(p-source), source);
+ return false;
+}
+
+bool ChecksumConverter::
+printPseudo(const StreamFormat& format, StreamBuffer& output)
+{
+ ulong sum;
+ int fnum = format.info[0];
+ int start = format.width;
+ int length = output.length()-format.width;
+ if (format.prec > 0) length -= format.prec;
+
+ debug("ChecksumConverter %s: output to check: \"%s\"\n",
+ checksumMap[fnum].name, output.expand(start,length)());
+
+ sum = checksumMap[fnum].xorout ^ checksumMap[fnum].func(
+ reinterpret_cast(output(start)), length,
+ checksumMap[fnum].init) & mask[checksumMap[fnum].bytes];
+
+ debug("ChecksumConverter %s: output checksum is 0x%lX\n",
+ checksumMap[fnum].name, sum);
+
+ int i;
+ unsigned outchar;
+
+ if (format.flags & alt_flag) // lsb first (little endian)
+ {
+ for (i = 0; i < checksumMap[fnum].bytes; i++)
+ {
+ outchar = sum & 0xff;
+ debug("ChecksumConverter %s: little endian appending 0x%X\n",
+ checksumMap[fnum].name, outchar);
+ if (format.flags & zero_flag) // ASCII
+ output.printf("%02X", outchar);
+ else // binary
+ output.append(outchar);
+ sum >>= 8;
+ }
+ }
+ else // msb first (big endian)
+ {
+ sum <<= 8*(4-checksumMap[fnum].bytes);
+ for (i = 0; i < checksumMap[fnum].bytes; i++)
+ {
+ outchar = (sum >> 24) & 0xff;
+ debug("ChecksumConverter %s: big endian appending 0x%X\n",
+ checksumMap[fnum].name, outchar);
+ if (format.flags & zero_flag) // ASCII
+ output.printf("%02X", outchar);
+ else // binary
+ output.append(outchar);
+ sum <<= 8;
+ }
+ }
+ return true;
+}
+
+int ChecksumConverter::
+scanPseudo(const StreamFormat& format, StreamBuffer& input, long& cursor)
+{
+ int fnum = format.info[0];
+ ulong sum;
+ int start = format.width;
+ int length = cursor-format.width;
+ if (format.prec > 0) length -= format.prec;
+
+ debug("ChecksumConverter %s: input to check: \"%s\n",
+ checksumMap[fnum].name, input.expand(start,length)());
+
+ if (input.length() - cursor <
+ (format.flags & zero_flag ? 2 : 1) * checksumMap[fnum].bytes)
+ {
+ error("Input too short for checksum\n");
+ return -1;
+ }
+
+ sum = checksumMap[fnum].xorout ^ checksumMap[fnum].func(
+ reinterpret_cast(input(start)), length,
+ 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);
+
+ int i,j;
+ unsigned inchar;
+
+ if (format.flags & alt_flag) // lsb first (little endian)
+ {
+ for (i = 0; i < checksumMap[fnum].bytes; i++)
+ {
+ if (format.flags & zero_flag) // ASCII
+ {
+ sscanf(input(cursor+2*i), "%2X", &inchar);
+ }
+ else // binary
+ {
+ inchar = input[cursor+i] & 0xff;
+ }
+ if (inchar != ((sum >> 8*i) & 0xff))
+ {
+ error("Input byte 0x%02X does not match checksum 0x%0*lX\n",
+ inchar, 2*checksumMap[fnum].bytes, sum);
+ return -1;
+ }
+ }
+ }
+ else // msb first (big endian)
+ {
+ for (i = checksumMap[fnum].bytes-1, j = 0; i >= 0; i--, j++)
+ {
+ if (format.flags & zero_flag) // ASCII
+ {
+ sscanf(input(cursor+2*i), "%2x", &inchar);
+ }
+ else // binary
+ {
+ inchar = input[cursor+i] & 0xff;
+ }
+ if (inchar != ((sum >> 8*j) & 0xff))
+ {
+ error("Input byte 0x%02X does not match checksum 0x%0*lX\n",
+ inchar, 2*checksumMap[fnum].bytes, sum);
+ return -1;
+ }
+ }
+ }
+ if (format.flags & zero_flag) // ASCII
+ return 2*checksumMap[fnum].bytes;
+ return checksumMap[fnum].bytes;
+}
+
+RegisterConverter (ChecksumConverter, "<");
diff --git a/src/DebugInterface.cc b/src/DebugInterface.cc
new file mode 100644
index 0000000..51da2a6
--- /dev/null
+++ b/src/DebugInterface.cc
@@ -0,0 +1,229 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is the interface to a debug and example bus drivers 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 "StreamBusInterface.h"
+#include "StreamError.h"
+#include "StreamBuffer.h"
+
+// This is a non-blocking bus interface for debugging purpose.
+// Normally, a bus interface will use blocking I/O and thus require
+// a separate thread.
+
+class DebugInterface : StreamBusInterface
+{
+ DebugInterface(Client* client);
+
+ // StreamBusInterface methods
+ bool lockRequest(unsigned long lockTimeout_ms);
+ bool unlock();
+ bool writeRequest(const void* output, size_t size,
+ unsigned long writeTimeout_ms);
+ bool readRequest(unsigned long replyTimeout_ms,
+ unsigned long readTimeout_ms, long expectedLength, bool async);
+
+protected:
+ ~DebugInterface();
+
+public:
+ // static creator method
+ static StreamBusInterface* getBusInterface(Client* client,
+ const char* busname, int addr, const char* param);
+};
+
+RegisterStreamBusInterface(DebugInterface);
+
+DebugInterface::
+DebugInterface(Client* client) : StreamBusInterface(client)
+{
+ // Create or attach to interface thread
+}
+
+DebugInterface::
+~DebugInterface()
+{
+ // Abort any running I/O
+ // Disconnect low-level bus driver
+ // Free all resources
+}
+
+// Interface method getBusInterface():
+// Do we have this bus/addr ?
+StreamBusInterface* DebugInterface::
+getBusInterface(Client* client,
+ const char* busname, int addr, const char*)
+{
+ if (strcmp(busname, "debug") == 0)
+ {
+ DebugInterface* interface = new DebugInterface(client);
+ debug ("DebugInterface::getBusInterface(%s, %d): "
+ "new Interface allocated\n",
+ busname, addr);
+ // Connect to low-level bus driver here or in constructor
+ // Delete interface and return NULL if connect fails.
+ return interface;
+ }
+ return NULL;
+}
+
+// Interface method lockRequest():
+// We want exclusive access to the device
+// lockTimeout_ms=0 means "block here" (used in @init)
+// Return false if the lock request cannot be accepted.
+bool DebugInterface::
+lockRequest(unsigned long lockTimeout_ms)
+{
+ debug("DebugInterface::lockRequest(%s, %ld msec)\n",
+ clientName(), lockTimeout_ms);
+
+ // Debug interface is non-blocking,
+ // thus we can call lockCallback() immediately.
+ lockCallback(StreamIoSuccess);
+
+ // A blocking interface would send a message that requests
+ // exclusive access to the interface thread.
+ // Once the interface is available, the interface thread
+ // would call lockCallback(StreamIoSuccess).
+ // If lockTimeout_ms expires, the interface would
+ // call lockCallback(ioTimeout).
+
+ // Only if lockTimeout_ms==0, a blocking interface is allowed,
+ // even required, to block here until the interface is available.
+
+ return true;
+}
+
+// Interface method unlock():
+// We don't need exclusive access any more
+// Return false if the unlock fails. (How can that be?)
+bool DebugInterface::
+unlock()
+{
+ debug("DebugInterface::unlock(%s)\n",
+ clientName());
+
+ // debug interface is non-blocking, thus unlock does nothing.
+
+ // A blocking interface would call lockCallback(StreamIoSuccess) now
+ // for the next interface that had called lockRequest.
+
+ return true;
+}
+
+// Interface method writeRequest():
+// We want to write something
+// This method will only be called by StreamDevice when locked.
+// I.e. lockRequest() has been called and the interface
+// has called lockCallback(StreamIoSuccess).
+// It may be called several times before unlock() is called.
+// Return false if the write request cannot be accepted.
+bool DebugInterface::
+writeRequest(const void* output, size_t size, unsigned long writeTimeout_ms)
+{
+ debug("DebugInterface::writeRequest(%s, \"%.*s\", %ld msec)\n",
+ clientName(), (int)size, (char*)output, writeTimeout_ms);
+
+ // Debug interface is non-blocking,
+ // thus we can call writeCallback() immediately.
+ writeCallback(StreamIoSuccess);
+
+ // A blocking interface would send a message that requests
+ // write access to the interface thread.
+ // That thread would handle the actual write.
+ // Writing all output might take some time and might be
+ // done in several chunks.
+ // If any chunk cannot be written within writeTimeout_ms milliseconds,
+ // the interface thread would call writeCallback(ioTimeout).
+ // If anything is wrong with the bus (e.g. unpugged cable),
+ // the interface would return false now or call writeCallback(ioFault)
+ // later when it finds out.
+ // Once the interface has completed writing, the interface thread
+ // would call writeCallback(StreamIoSuccess).
+
+ return true;
+}
+
+// Interface method readRequest():
+// We want to read something
+// This method may be called in async mode, if a previous call to
+// supportsAsyncRead() had returned true.
+// It may be called unlocked.
+// If expectedLength!=0, it is a hint when to stop reading input.
+// Return false if the read request cannot be accepted.
+bool DebugInterface::
+readRequest(unsigned long replyTimeout_ms, unsigned long readTimeout_ms,
+ long expectedLength, bool async)
+{
+ debug("DebugInterface::readRequest(%s, %ld msec reply, %ld msec read, expect %ld bytes, asyn=%s)\n",
+ clientName(), replyTimeout_ms, readTimeout_ms, expectedLength, async?"yes":"no");
+
+ // Debug interface does not support async mode.
+ // Since we have not implemented supportsAsyncRead(),
+ // this method is never called with async=true.
+ if (async) return false;
+
+ // Debug interface is non-blocking,
+ // thus we can call writeCallback() immediately.
+ const char input [] = "Receviced input 3.1415\r\n";
+ readCallback(StreamIoEnd, input, sizeof(input));
+
+ // A blocking interface would send a message that requests
+ // read access to the interface thread.
+ // That thread would handle the actual read.
+ // Reading all input might take some time and might be
+ // done in several chunks.
+ // If no data arrives within replyTimeout_ms milliseconds,
+ // the interface thread would call
+ // readCallback(ioNoReply).
+ // If any following chunk cannot be read within readTimeout_ms
+ // milliseconds, the interface thread would call
+ // readCallback(ioTimeout, input, inputlength)
+ // with all input received so far.
+ // For each reveived chunk, the interface would call
+ // readCallback(StreamIoSuccess, input, inputlength)
+ // and check its return value.
+ // The return value is 0 if no more input is expected, i.e.
+ // the interface would tell the low-level driver that
+ // reading has finished (if necessary) and return.
+ // If the return value is >0, this number of additional bytes is
+ // expected at maximum (this replaces the original expectedLengh).
+ // If the return value is -1, an unknown number of additional
+ // bytes is expected.
+ // In the both cases where more input is expected, the interface
+ // would do another read and call readCallback() again.
+ // The return value needs only to be checked if readCallback()
+ // is called with StreamIoSuccess as the first argument.
+ // If the interface received a signal from the low-level driver
+ // telling that input has terminated (e.g. EOS in GPIB, EOF when
+ // reading file or socket, ...) it would call
+ // readCallback(StreamIoEnd, input, inputlength).
+ // If anything is wrong with the bus (e.g. unpugged cable),
+ // the interface would immediately return false now or
+ // call readCallback(ioFault) later when it finds out.
+
+ // On some busses, input may arrive even before readRequest()
+ // has been called, e.g. when a device replies very fast.
+ // In this case, the interface would buffer the input or call
+ // readCallback() even without beeing asked to do so.
+
+ // In async mode, the interface would arrange that the client
+ // gets a copy of the next input on the same bus/address,
+ // even when the input is meant for an other client.
+
+ return true;
+}
diff --git a/src/EnumConverter.cc b/src/EnumConverter.cc
new file mode 100644
index 0000000..0a89fee
--- /dev/null
+++ b/src/EnumConverter.cc
@@ -0,0 +1,142 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is the enum 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"
+#include "StreamProtocol.h"
+
+// Enum %{string0|string1|...}
+
+class EnumConverter : public StreamFormatConverter
+{
+ int parse(const StreamFormat&, StreamBuffer&, const char*&, bool);
+ bool printLong(const StreamFormat&, StreamBuffer&, long);
+ int scanLong(const StreamFormat&, const char*, long&);
+};
+
+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))
+ {
+ 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;
+ while (*source)
+ {
+ switch (*source)
+ {
+ 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);
+ }
+ source++;
+ }
+ error("Missing '}' after %%{ format conversion\n");
+ return false;
+}
+
+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--)
+ {
+ while(*s)
+ {
+ if (*s == esc) s++;
+ s++;
+ }
+ s++;
+ }
+ while(*s)
+ {
+ if (*s == esc) s++;
+ output.append(*s++);
+ }
+ return true;
+}
+
+int EnumConverter::
+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
+ int length;
+ long val;
+ bool match;
+ for (val = 0; val <= maxValue; val++)
+ {
+ debug("EnumConverter::scanLong: check #%ld \"%s\"\n", val, s);
+ length = 0;
+ match = true;
+ while(*s)
+ {
+ if (*s == StreamProtocolParser::skip)
+ {
+ s++;
+ length++;
+ continue;
+ }
+ if (*s == esc) s++;
+ if (*s++ != input[length++]) match = false;
+ }
+ if (match)
+ {
+ debug("EnumConverter::scanLong: value %ld matches\n", val);
+ value = val;
+ return length;
+ }
+ s++;
+ }
+ debug("EnumConverter::scanLong: no value matches\n");
+ return -1;
+}
+
+RegisterConverter (EnumConverter, "{");
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..7940e8c
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,100 @@
+################################################################
+# StreamDevice Support #
+# #
+# (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) #
+# (C) 2006 Dirk Zimoch (dirk.zimoch@psi.ch) #
+# #
+# This is the EPICS 3.14 Makefile of StreamDevice. #
+# Normally it should not be necessary to modify this file. #
+# All configuration can be done in CONFIG_STREAM #
+# #
+# 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. #
+# #
+################################################################
+
+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
+
+-include CONFIG_STREAM
+-include ../CONFIG_STREAM
+
+LIBRARY_DEFAULT = stream
+
+DBD += $(LIBRARY_DEFAULT).dbd
+
+ifdef ASYN
+LIB_LIBS += asyn
+BUSSES += AsynDriver
+endif
+
+ifdef T_A
+ifndef BUSSES
+$(error No bus interface defined! Didn't you set ASYN in your RELEASE file?)
+endif
+endif
+
+ifeq ($(LOADABLE_MODULE),YES)
+SRCS += $(LIBRARY_DEFAULT)_registerRecordDeviceDriver.cpp
+endif
+SRCS += $(BUSSES:%=%Interface.cc)
+SRCS += $(FORMATS:%=%Converter.cc)
+SRCS += $(RECORDS:%=dev%Stream.c)
+SRCS += $(STREAM_SRCS)
+
+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
+
+INC += devStream.h
+
+include $(TOP)/configure/RULES
+
+# Update version string (contains __DATE__ and __TIME__)
+# each time make runs.
+StreamVersion$(OBJ): FORCE
+FORCE:
+
+# Add references to all registrars to main file to avoid
+# missing initialization.
+StreamCore$(OBJ): 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 >> $@
+
+# 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 "registrar(streamRegistrar)" >> $@
+
+endif
diff --git a/src/Makefile.Host b/src/Makefile.Host
new file mode 100644
index 0000000..659ea6d
--- /dev/null
+++ b/src/Makefile.Host
@@ -0,0 +1,45 @@
+################################################################
+# StreamDevice Support #
+# #
+# (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) #
+# (C) 2006 Dirk Zimoch (dirk.zimoch@psi.ch) #
+# #
+# This is the EPICS 3.13 Makefile of StreamDevice. #
+# Normally it should not be necessary to modify this file. #
+# All configuration can be done in CONFIG_STREAM #
+# #
+# 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. #
+# #
+################################################################
+
+TOP = ../../..
+
+include $(TOP)/config/CONFIG_APP
+include ../CONFIG_STREAM
+
+# In 3.13, calcout has no device support
+RECORDS_3_13 = $(filter-out calcout,$(RECORDS))
+
+DBDNAME = stream.dbd
+
+INC += devStream.h
+
+# This is the munch.pl taken from EPICS 3.14.8.2
+# Install script and rule.
+CONFIGS = RULES.munch
+SCRIPTS = munch.pl
+
+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)" >> $@
diff --git a/src/Makefile.Vx b/src/Makefile.Vx
new file mode 100644
index 0000000..e95ce60
--- /dev/null
+++ b/src/Makefile.Vx
@@ -0,0 +1,58 @@
+################################################################
+# StreamDevice Support #
+# #
+# (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) #
+# (C) 2006 Dirk Zimoch (dirk.zimoch@psi.ch) #
+# #
+# This is the EPICS 3.13 Makefile of StreamDevice. #
+# Normally it should not be necessary to modify this file. #
+# All configuration can be done in CONFIG_STREAM #
+# #
+# 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. #
+# #
+################################################################
+
+TOP = ../../..
+
+include $(TOP)/config/CONFIG_APP
+include ../CONFIG_STREAM
+
+LIBNAME = streamLib
+
+# In 3.13, calcout has no device support
+RECORDS_3_13 = $(filter-out calcout,$(RECORDS))
+
+ifdef ASYN
+BUSSES += AsynDriver
+endif
+
+SRCS.cc += $(patsubst %,../%,$(filter %.cc,$(STREAM_SRCS)))
+SRCS.cc += $(BUSSES:%=../%Interface.cc)
+SRCS.cc += $(FORMATS:%=../%Converter.cc)
+SRCS.c += $(patsubst %,../%,$(filter %.c,$(STREAM_SRCS)))
+SRCS.c += $(RECORDS_3_13:%=../dev%Stream.c)
+
+LIBOBJS = $(patsubst ../%,%.o,$(basename $(SRCS.cc) $(SRCS.c)))
+
+include $(TOP)/config/RULES.Vx
+include $(TOP)/config/RULES.munch
+
+build:: depends
+
+-include DEPENDS
+
+# Update version string (contains __DATE__ and __TIME__)
+# each time make runs.
+StreamVersion.o: FORCE
+FORCE:
+
+StreamCore.o: streamReferences
+
+streamReferences:
+ touch $@
diff --git a/src/RULES.munch b/src/RULES.munch
new file mode 100644
index 0000000..816a3b3
--- /dev/null
+++ b/src/RULES.munch
@@ -0,0 +1,14 @@
+MUNCH = $(PERL) $(INSTALL_LOCATION)/bin/$(HOST_ARCH)/munch.pl
+
+# The original 3.13.10 munching rule does not really work well
+
+build:: $(LIBNAME).munch
+
+buildInstall:: $(INSTALL_BIN)/$(LIBNAME).munch
+
+%.munch: %
+ $(RM) $*_ctct.o $*_ctdt.c
+ $(NM) $< | $(MUNCH) > $*_ctdt.c
+ $(GCC) -traditional $(CFLAGS) -fdollars-in-identifiers -c $(SOURCE_FLAG) $*_ctdt.c
+ $(LINK.c) $@ $< $*_ctdt.o
+
diff --git a/src/RawConverter.cc b/src/RawConverter.cc
new file mode 100644
index 0000000..d057665
--- /dev/null
+++ b/src/RawConverter.cc
@@ -0,0 +1,119 @@
+/***************************************************************
+* 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"
+
+// Raw Bytes Converter %r
+
+class RawConverter : public StreamFormatConverter
+{
+ int parse(const StreamFormat&, StreamBuffer&, const char*&, bool);
+ bool printLong(const StreamFormat&, StreamBuffer&, long);
+ int scanLong(const StreamFormat&, const char*, long&);
+};
+
+int RawConverter::
+parse(const StreamFormat&, StreamBuffer&,
+ const char*&, bool)
+{
+ return long_format;
+}
+
+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
+ if (format.width > width) width = format.width;
+ char byte = 0;
+ if (format.flags & alt_flag) // lsb first (little endian)
+ {
+ while (prec--)
+ {
+ byte = static_cast(value);
+ output.append(byte);
+ value >>= 8;
+ width--;
+ }
+ byte = (byte & 0x80) ? 0xFF : 0x00; // fill with sign
+ while (width--)
+ {
+ output.append(byte);
+ }
+ }
+ else // msb first (big endian)
+ {
+ byte = ((value >> (8 * (prec-1))) & 0x80) ? 0xFF : 0x00;
+ while (width > prec) // fill with sign
+ {
+ output.append(byte);
+ width--;
+ }
+ while (prec--)
+ {
+ output.append(static_cast(value >> (8 * prec)));
+ }
+ }
+ return true;
+}
+
+int RawConverter::
+scanLong(const StreamFormat& format, const char* input, long& value)
+{
+ long length = 0;
+ long val = 0;
+ int width = format.width;
+ if (width == 0) width = 1; // default: 1 byte
+ if (format.flags & skip_flag)
+ {
+ return width; // just skip input
+ }
+ if (format.flags & alt_flag)
+ {
+ // little endian (sign extended)*/
+ unsigned int shift = 0;
+ while (--width && shift < sizeof(long)*8)
+ {
+ val |= ((unsigned char) input[length++]) << shift;
+ shift += 8;
+ }
+ if (width == 0)
+ {
+ val |= ((signed char) input[length++]) << shift;
+ }
+ length += width; // ignore upper bytes not fitting in long
+ }
+ else
+ {
+ // big endian (sign extended)*/
+ val = (signed char) input[length++];
+ while (--width)
+ {
+ val <<= 8;
+ val |= (unsigned char) input[length++];
+ }
+ }
+ value = val;
+ return length;
+}
+
+RegisterConverter (RawConverter, "r");
diff --git a/src/StreamBuffer.cc b/src/StreamBuffer.cc
new file mode 100644
index 0000000..34c3eab
--- /dev/null
+++ b/src/StreamBuffer.cc
@@ -0,0 +1,317 @@
+/***************************************************************
+* StreamBuffer *
+* *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is a buffer class used in StreamDevice for I/O. *
+* 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 "StreamBuffer.h"
+#include "StreamError.h"
+
+#include
+#include
+#include
+
+#if defined(__vxworks) || defined(vxWorks) || defined(_WIN32) || defined(__rtems__)
+// These systems have no vsnprintf
+#include
+#define vsnprintf epicsVsnprintf
+#endif
+
+void StreamBuffer::
+init(const void* s, long minsize)
+{
+ len = 0;
+ offs = 0;
+ buffer = local;
+ cap = sizeof(local);
+ if (minsize < 0) minsize = 0;
+ if (minsize >= cap)
+ {
+ // use allocated buffer
+ grow(minsize);
+ }
+ else
+ {
+ // clear local buffer
+ memset(buffer+minsize, 0, cap-minsize);
+ }
+ if (s) {
+ len = minsize;
+ memcpy(buffer, s, minsize);
+ }
+}
+
+void StreamBuffer::
+grow(long minsize)
+{
+ // make space for minsize + 1 (for termination) bytes
+ char* newbuffer;
+ long newcap;
+ if (minsize > 10000)
+ {
+ // crude trap against infinite grow
+ error ("StreamBuffer exploded growing from %ld to %ld chars. Exiting\n",
+ cap, minsize);
+ int i;
+ char c;
+ fprintf(stderr, "String contents (len=%ld):\n", len);
+ for (i = offs; i < len; i++)
+ {
+ c = buffer[i];
+ if ((c & 0x7f) < 0x20 || (c & 0x7f) == 0x7f)
+ {
+ fprintf(stderr, "<%02x>", c & 0xff);
+ }
+ else
+ {
+ fprintf(stderr, "%c", c);
+ }
+ }
+ fprintf(stderr, "\n");
+ abort();
+ }
+ if (minsize < cap)
+ {
+ // just move contents to start of buffer and clear end
+ // to avoid reallocation
+ memmove(buffer, buffer+offs, len);
+ memset(buffer+len, 0, offs);
+ offs = 0;
+ return;
+ }
+ // allocate new buffer
+ for (newcap = sizeof(local)*2; newcap <= minsize; newcap *= 2);
+ newbuffer = new char[newcap];
+ // copy old buffer to new buffer and clear end
+ memcpy(newbuffer, buffer+offs, len);
+ memset(newbuffer+len, 0, newcap-len);
+ if (buffer != local)
+ {
+ delete buffer;
+ }
+ buffer = newbuffer;
+ cap = newcap;
+ offs = 0;
+}
+
+StreamBuffer& StreamBuffer::
+append(const void* s, long size)
+{
+ if (size <= 0)
+ {
+ // append negative number of bytes? let's delete some
+ if (size < -len) size = -len;
+ memset (buffer+offs+len+size, 0, -size);
+ }
+ else
+ {
+ check(size);
+ memcpy(buffer+offs+len, s, size);
+ }
+ len += size;
+ return *this;
+}
+
+long int StreamBuffer::
+find(const void* m, long size, long start) const
+{
+ if (start < 0)
+ {
+ start += len;
+ if (start < 0) start = 0;
+ }
+ if (start >= len-size+1) return -1; // find nothing after end
+ if (!m || size <= 0) return start; // find empty string at start
+ const char* s = static_cast(m);
+ char* b = buffer+offs;
+ char* p = b+start;
+ long i;
+ while ((p = static_cast(memchr(p, s[0], b-p+len-size+1))))
+ {
+ i = 1;
+ while (p[i] == s[i])
+ {
+ if (++i >= size) return p-b;
+ }
+ p++;
+ }
+ return -1;
+}
+
+StreamBuffer& StreamBuffer::
+replace(long remstart, long remlen, const void* ins, long inslen)
+{
+ if (remstart < 0)
+ {
+ // remove from end
+ remstart += len;
+ }
+ if (remlen < 0)
+ {
+ // remove left of remstart
+ remstart += remlen;
+ remlen = -remlen;
+ }
+ if (inslen < 0)
+ {
+ // handle negative inserts as additional remove
+ remstart += inslen;
+ remlen -= inslen;
+ inslen = 0;
+ }
+ if (remstart < 0)
+ {
+ // truncate remove before bufferstart
+ remlen += remstart;
+ remstart = 0;
+ }
+ if (remstart > len)
+ {
+ // remove begins after bufferend
+ remstart = len;
+ }
+ if (remlen >= len-remstart)
+ {
+ // truncate remove after bufferend
+ remlen = len-remstart;
+ }
+ if (inslen == 0 && remstart == 0)
+ {
+ // optimize remove of bufferstart
+ offs += remlen;
+ return *this;
+ }
+ if (inslen < 0) inslen = 0;
+ long remend = remstart+remlen;
+ long newlen = len+inslen-remlen;
+ if (cap <= newlen)
+ {
+ // buffer too short
+ long newcap;
+ for (newcap = sizeof(local)*2; newcap <= newlen; newcap *= 2);
+ char* newbuffer = new char[newcap];
+ memcpy(newbuffer, buffer+offs, remstart);
+ memcpy(newbuffer+remstart, ins, inslen);
+ memcpy(newbuffer+remstart+inslen, buffer+offs+remend, len-remend);
+ memset(newbuffer+newlen, 0, newcap-newlen);
+ if (buffer != local)
+ {
+ delete buffer;
+ }
+ buffer = newbuffer;
+ cap = newcap;
+ offs = 0;
+ }
+ else
+ {
+ if (newlen+offs<=cap)
+ {
+ // move to start of buffer
+ memmove(buffer+offs+remstart+inslen, buffer+offs+remend, len-remend);
+ memcpy(buffer+offs+remstart, ins, inslen);
+ if (newlen -1 && printed < (int)(cap-offs-len))
+ {
+ len += printed;
+ return *this;
+ }
+ if (printed > -1) grow(len+printed);
+ else grow(len);
+ }
+}
+
+StreamBuffer StreamBuffer::expand(long start, long length) const
+{
+ long end;
+ if (start < 0)
+ {
+ start += len;
+ if (start < 0) start = 0;
+ }
+ end = length >= 0 ? start+length : len;
+ if (end > len) end = len;
+ StreamBuffer result((end-start)*2);
+ start += offs;
+ end += offs;
+ long i;
+ char c;
+ for (i = start; i < end; i++)
+ {
+ c = buffer[i];
+ if ((c & 0x7f) < 0x20 || (c & 0x7f) == 0x7f)
+ {
+ result.printf("<%02x>", c & 0xff);
+ }
+ else
+ {
+ result.append(c);
+ }
+ }
+ return result;
+}
+
+StreamBuffer StreamBuffer::
+dump() const
+{
+ StreamBuffer result(256+cap*5);
+ result.append("\033[0m");
+ long i;
+ result.printf("%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(
+ "<%02x>", buffer[i] & 0xff);
+ else
+ result.printf(
+ "\033[34m<%02x>\033[37m", buffer[i] & 0xff);
+ }
+ else
+ {
+ result.append(buffer[i]);
+ }
+ if (i == offs+len-1) result.append("\033[34m]\033[37m");
+ }
+ result.append("\033[0m");
+ return result;
+}
diff --git a/src/StreamBuffer.h b/src/StreamBuffer.h
new file mode 100644
index 0000000..6789c09
--- /dev/null
+++ b/src/StreamBuffer.h
@@ -0,0 +1,208 @@
+/***************************************************************
+* StreamBuffer *
+* *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is a buffer class used in StreamDevice for I/O. *
+* 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. *
+* *
+***************************************************************/
+
+#ifndef StreamBuffer_h
+#define StreamBuffer_h
+
+#include
+#include
+
+#ifndef __GNUC__
+#define __attribute__(x)
+#endif
+
+class StreamBuffer
+{
+ long len;
+ long cap;
+ long offs;
+ char* buffer;
+ char local[64];
+
+ void grow(long);
+ void init(const void*, long);
+
+ void check(long size)
+ {if (len+offs+size >= cap) grow(len+size);}
+
+public:
+ // Hints:
+ // * Any index parameter (long) can be negative
+ // meaning "count from end" (-1 is the last byte)
+ // * Any returned char* pointer becomes invalid when
+ // the StreamBuffer is modified.
+
+ StreamBuffer()
+ {init(NULL, 0);}
+
+ StreamBuffer(const void*s, long size)
+ {init(s, size);}
+
+ StreamBuffer(const char*s)
+ {init(s, s?strlen(s):0);}
+
+ StreamBuffer(const StreamBuffer& s)
+ {init(s.buffer+s.offs, s.len);}
+
+ StreamBuffer(long size)
+ {init(NULL, size);}
+
+ ~StreamBuffer()
+ {if (buffer != local) delete buffer;}
+
+ // 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);}
+
+ char* operator()(long index=0)
+ {buffer[offs+len]=0; return buffer+offs+(index<0?index+len:index);}
+
+ // operator []: get byte at index
+ char operator[](long index) const
+ {return buffer[offs+(index<0?index+len:index)];}
+
+ char& operator[](long index)
+ {return buffer[offs+(index<0?index+len:index)];}
+
+ // cast to bool: not empty?
+ operator bool() const
+ {return len>0;}
+
+ // length: get current data length
+ long length() const
+ {return len;}
+
+ // capacity: get current max data length (spare one byte for end)
+ long capacity() const
+ {return cap-1;}
+
+ // end: get pointer to byte after last data byte
+ const char* end() const
+ {return buffer+offs+len;}
+
+ // clear: set length to 0, don't free or blank memory (fast!)
+ StreamBuffer& clear()
+ {offs+=len; len=0; return *this;}
+
+ // 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; }
+
+ // append: append data at the end of the buffer
+ StreamBuffer& append(char c)
+ {check(1); buffer[offs+len++]=c; return *this;}
+
+ StreamBuffer& append(const void* s, long size);
+
+ StreamBuffer& append(const char* s)
+ {return append(s, s?strlen(s):0);}
+
+ StreamBuffer& append(const StreamBuffer& s)
+ {return append(s.buffer+s.offs, s.len);}
+
+ // set: clear buffer and fill with new data
+ StreamBuffer& set(const void* s, long size)
+ {clear(); return append(s, size);}
+
+ StreamBuffer& set(const char* s)
+ {clear(); return append(s, s?strlen(s):0);}
+
+ StreamBuffer& set(const StreamBuffer& s)
+ {clear(); return append(s.buffer+s.offs, s.len);}
+
+ // operator =: alias for set
+ StreamBuffer& operator=(const char* s)
+ {return set(s);}
+
+ StreamBuffer& operator=(const StreamBuffer& s)
+ {return set(s);}
+
+ // replace: delete part of buffer (pos/length) and insert new data
+ StreamBuffer& replace(
+ long pos, long length, const void* s, long size);
+
+ StreamBuffer& replace(long pos, long length, const char* s)
+ {return replace(pos, length, s, s?strlen(s):0);}
+
+ StreamBuffer& replace(long pos, long length, const StreamBuffer& s)
+ {return replace(pos, length, s.buffer+s.offs, s.len);}
+
+ // replace: delete part of buffer
+ StreamBuffer& remove(long pos, long length)
+ {return replace(pos, length, NULL, 0);}
+
+ StreamBuffer& remove(long length)
+ {if (length>len) length=len;
+ offs+=length; len-=length; return *this;}
+
+ // replace: delete end of buffer
+ StreamBuffer& truncate(long pos)
+ {return replace(pos, len, NULL, 0);}
+
+ // insert: insert new data into buffer
+ StreamBuffer& insert(long pos, const void* s, long size)
+ {return replace(pos, 0, s, size);}
+
+ StreamBuffer& insert(long pos, const char* s)
+ {return replace(pos, 0, s, s?strlen(s):0);}
+
+ StreamBuffer& insert(long pos, const StreamBuffer& s)
+ {return replace(pos, 0, s.buffer+s.offs, s.len);}
+
+ StreamBuffer& insert(long pos, char c)
+ {return replace(pos, 0, &c, 1);}
+
+ StreamBuffer& printf(const char* fmt, ...)
+ __attribute__ ((format(printf,2,3)));
+
+ // find: get index of data in buffer or -1
+ long find(char c, long start=0) const
+ {char* p;
+ return (p = static_cast(
+ memchr(buffer+offs+(start<0?start+len:start),
+ c, start<0?-start:len-start)))?
+ p-(buffer+offs) : -1;}
+
+ long find(const void* s, long size, long start=0) const;
+
+ long find(const char* s, long start=0) const
+ {return find(s, s?strlen(s):0, start);}
+
+ 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
+ {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
+ {return len ? strcmp(buffer+offs, s) == 0 : !s || !*s;}
+
+// expand: create copy of StreamBuffer where all nonprintable characters
+// are replaced by with xx being the hex code of the characters
+ StreamBuffer expand(long start=0, long length=-1) const;
+
+// dump: debug function, like expand but also show the 'hidden' memory
+// before and after the real data. Uses colours.
+ StreamBuffer dump() const;
+};
+
+#endif
diff --git a/src/StreamBusInterface.cc b/src/StreamBusInterface.cc
new file mode 100644
index 0000000..d3d7f8d
--- /dev/null
+++ b/src/StreamBusInterface.cc
@@ -0,0 +1,144 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is the interface to bus drivers 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 "StreamBusInterface.h"
+
+const char* StreamIoStatusStr[] = {
+ "StreamIoSuccess", "ioTimeout", "ioNoReply", "ioEnd", "ioFault"
+};
+
+StreamBusInterfaceRegistrarBase* StreamBusInterfaceRegistrarBase::first;
+
+StreamBusInterfaceRegistrarBase::
+StreamBusInterfaceRegistrarBase(const char* name) : name(name)
+{
+ next = NULL;
+ StreamBusInterfaceRegistrarBase** pr;
+ for (pr = &first; *pr; pr = &(*pr)->next);
+ *pr = this;
+}
+
+StreamBusInterfaceRegistrarBase::
+~StreamBusInterfaceRegistrarBase()
+{
+}
+
+StreamBusInterface::
+StreamBusInterface(Client* client) :
+ client(client)
+{
+}
+
+bool StreamBusInterface::
+supportsEvent()
+{
+ return false;
+}
+
+bool StreamBusInterface::
+supportsAsyncRead()
+{
+ return false;
+}
+
+StreamBusInterface* StreamBusInterface::
+find(Client* client, const char* busname, int addr, const char* param)
+{
+ StreamBusInterfaceRegistrarBase* r;
+ StreamBusInterface* bus;
+ for (r = r->first; r; r = r->next)
+ {
+ bus = r->find(client, busname, addr, param);
+ if (bus) return bus;
+ }
+ return NULL;
+}
+
+bool StreamBusInterface::
+acceptEvent(unsigned long, unsigned long)
+{
+ return false;
+}
+
+void StreamBusInterface::
+release ()
+{
+ delete this;
+}
+
+bool StreamBusInterface::
+connectRequest (unsigned long)
+{
+ return false;
+}
+
+bool StreamBusInterface::
+disconnect ()
+{
+ return false;
+}
+
+bool StreamBusInterface::
+writeRequest(const void*, size_t, unsigned long)
+{
+ return false;
+}
+
+bool StreamBusInterface::
+readRequest(unsigned long, unsigned long, long, bool)
+{
+ return false;
+}
+
+void StreamBusInterface::
+finish()
+{
+}
+
+StreamBusInterface::Client::
+~Client()
+{
+}
+
+void StreamBusInterface::Client::
+writeCallback(StreamIoStatus)
+{
+}
+
+long StreamBusInterface::Client::
+readCallback(StreamIoStatus, const void*, long)
+{
+ return 0;
+}
+
+void StreamBusInterface::Client::
+eventCallback(StreamIoStatus)
+{
+}
+
+void StreamBusInterface::Client::
+connectCallback(StreamIoStatus)
+{
+}
+
+long StreamBusInterface::Client::
+priority()
+{
+ return 0;
+}
diff --git a/src/StreamBusInterface.h b/src/StreamBusInterface.h
new file mode 100644
index 0000000..17f0c66
--- /dev/null
+++ b/src/StreamBusInterface.h
@@ -0,0 +1,200 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is the interface to bus drivers 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. *
+* *
+***************************************************************/
+
+#ifndef StreamBusInterface_h
+#define StreamBusInterface_h
+
+#include
+
+enum StreamIoStatus {
+ StreamIoSuccess, StreamIoTimeout, StreamIoNoReply,
+ StreamIoEnd, StreamIoFault
+};
+
+extern const char* StreamIoStatusStr[];
+
+class StreamBusInterface
+{
+public:
+
+ class Client
+ {
+ friend class StreamBusInterface;
+ virtual void lockCallback(StreamIoStatus status) = 0;
+ virtual void writeCallback(StreamIoStatus status);
+ virtual long readCallback(StreamIoStatus status,
+ const void* input, long size);
+ virtual void eventCallback(StreamIoStatus status);
+ virtual void connectCallback(StreamIoStatus status);
+ virtual long priority();
+ virtual const char* name() = 0;
+ virtual const char* getInTerminator(size_t& length) = 0;
+ virtual const char* getOutTerminator(size_t& length) = 0;
+ public:
+ virtual ~Client();
+ protected:
+ StreamBusInterface* businterface;
+ bool busSupportsEvent() {
+ return businterface->supportsEvent();
+ }
+ bool busSupportsAsyncRead() {
+ return businterface->supportsAsyncRead();
+ }
+ bool busAcceptEvent(unsigned long mask,
+ unsigned long replytimeout_ms) {
+ return businterface->acceptEvent(mask, replytimeout_ms);
+ }
+ void busRelease() {
+ businterface->release();
+ }
+ bool busLockRequest(unsigned long timeout_ms) {
+ return businterface->lockRequest(timeout_ms);
+ }
+ bool busUnlock() {
+ return businterface->unlock();
+ }
+ bool busWriteRequest(const void* output, size_t size,
+ unsigned long timeout_ms) {
+ return businterface->writeRequest(output, size, timeout_ms);
+ }
+ bool busReadRequest(unsigned long replytimeout_ms,
+ unsigned long readtimeout_ms, long expectedLength,
+ bool async) {
+ return businterface->readRequest(replytimeout_ms,
+ readtimeout_ms, expectedLength, async);
+ }
+ void busFinish() {
+ businterface->finish();
+ }
+ bool busConnectRequest(unsigned long timeout_ms) {
+ return businterface->connectRequest(timeout_ms);
+ }
+ bool busDisconnect() {
+ return businterface->disconnect();
+ }
+ };
+
+private:
+ friend class StreamBusInterfaceClass; // the iterator
+ friend class Client;
+
+public:
+ Client* client;
+ virtual ~StreamBusInterface() {};
+
+protected:
+ StreamBusInterface(Client* client);
+
+// map client functions into StreamBusInterface namespace
+ void lockCallback(StreamIoStatus status)
+ { client->lockCallback(status); }
+ void writeCallback(StreamIoStatus status)
+ { client->writeCallback(status); }
+ long readCallback(StreamIoStatus status,
+ const void* input = NULL, long size = 0)
+ { return client->readCallback(status, input, size); }
+ void eventCallback(StreamIoStatus status)
+ { client->eventCallback(status); }
+ void connectCallback(StreamIoStatus status)
+ { client->connectCallback(status); }
+ const char* getInTerminator(size_t& length)
+ { return client->getInTerminator(length); }
+ const char* getOutTerminator(size_t& length)
+ { return client->getOutTerminator(length); }
+ long priority() { return client->priority(); }
+ const char* clientName() { return client->name(); }
+
+// default implementations
+ virtual bool writeRequest(const void* output, size_t size,
+ unsigned long timeout_ms);
+ virtual bool readRequest(unsigned long replytimeout_ms,
+ unsigned long readtimeout_ms, long expectedLength,
+ bool async);
+ virtual bool supportsEvent(); // defaults to false
+ virtual bool supportsAsyncRead(); // defaults to false
+ virtual bool acceptEvent(unsigned long mask, // implement if
+ unsigned long replytimeout_ms); // supportsEvents() returns true
+ virtual void release();
+ virtual bool connectRequest(unsigned long connecttimeout_ms);
+ virtual bool disconnect();
+ virtual void finish();
+
+// pure virtual
+ virtual bool lockRequest(unsigned long timeout_ms) = 0;
+ virtual bool unlock() = 0;
+
+public:
+// static methods
+ static StreamBusInterface* find(Client*, const char* busname,
+ int addr, const char* param);
+};
+
+class StreamBusInterfaceRegistrarBase
+{
+ friend class StreamBusInterfaceClass; // the iterator
+ friend class StreamBusInterface; // the implementation
+ static StreamBusInterfaceRegistrarBase* first;
+ StreamBusInterfaceRegistrarBase* next;
+ virtual StreamBusInterface* find(StreamBusInterface::Client* client,
+ const char* busname, int addr, const char* param) = 0;
+protected:
+ const char* name;
+ StreamBusInterfaceRegistrarBase(const char* name);
+ virtual ~StreamBusInterfaceRegistrarBase();
+};
+
+template
+class StreamBusInterfaceRegistrar : protected StreamBusInterfaceRegistrarBase
+{
+ StreamBusInterface* find(StreamBusInterface::Client* client,
+ const char* busname, int addr, const char* param)
+ { return C::getBusInterface(client, busname, addr, param); }
+public:
+ StreamBusInterfaceRegistrar(const char* name) :
+ StreamBusInterfaceRegistrarBase(name) {};
+};
+
+
+#define RegisterStreamBusInterface(interface) \
+template class StreamBusInterfaceRegistrar; \
+StreamBusInterfaceRegistrar \
+registrar_##interface(#interface); \
+void* ref_##interface = ®istrar_##interface\
+
+// Interface class iterator
+
+class StreamBusInterfaceClass
+{
+ StreamBusInterfaceRegistrarBase* ptr;
+public:
+ StreamBusInterfaceClass () {
+ ptr = StreamBusInterfaceRegistrarBase::first;
+ }
+ StreamBusInterfaceClass& operator ++ () {
+ ptr = ptr->next; return *this;
+ }
+ const char* name() {
+ return ptr->name;
+ }
+ operator bool () {
+ return ptr != NULL;
+ }
+};
+
+#endif
diff --git a/src/StreamCore.cc b/src/StreamCore.cc
new file mode 100644
index 0000000..47ee904
--- /dev/null
+++ b/src/StreamCore.cc
@@ -0,0 +1,1517 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is the kernel 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 "StreamCore.h"
+#include "StreamError.h"
+#include
+#include
+
+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",
+ "connect", "disconnect" };
+
+inline const char* commandName(unsigned char i)
+{
+ return i > exec_cmd ? "invalid" : commandStr[i];
+}
+
+/// debug functions /////////////////////////////////////////////
+
+static char* printCommands(StreamBuffer& buffer, const char* c)
+{
+ unsigned long timeout;
+ unsigned long eventnumber;
+ unsigned short cmdlen;
+ while (1)
+ {
+ switch(*c++)
+ {
+ case end_cmd:
+ return buffer();
+ case in_cmd:
+ buffer.append(" in \"");
+ c = StreamProtocolParser::printString(buffer, c);
+ buffer.append("\";\n");
+ break;
+ case out_cmd:
+ buffer.append(" out \"");
+ c = StreamProtocolParser::printString(buffer, c);
+ buffer.append("\";\n");
+ break;
+ case wait_cmd:
+ timeout = extract(c);
+ buffer.printf(" wait %ld;\n # ms", timeout);
+ break;
+ case event_cmd:
+ eventnumber = extract(c);
+ timeout = extract(c);
+ buffer.printf(" event(%ld) %ld; # ms\n", eventnumber, timeout);
+ break;
+ case exec_cmd:
+ buffer.append(" exec \"");
+ cmdlen = extract(c);
+ c = StreamProtocolParser::printString(buffer, c);
+ buffer.append("\";\n");
+ break;
+ case connect_cmd:
+ timeout = extract(c);
+ buffer.printf(" connect %ld; # ms\n", timeout);
+ break;
+ case disconnect_cmd:
+ buffer.append(" disconnect;\n");
+ break;
+ default:
+ buffer.append("\033[31;1mGARBAGE: ");
+ c = StreamProtocolParser::printString(buffer, c-1);
+ buffer.append("\033[0m\n");
+ }
+ }
+}
+
+void StreamCore::
+printProtocol()
+{
+ StreamBuffer buffer;
+ printf("%s {\n", protocolname());
+ printf(" extraInput = %s;\n",
+ (flags & IgnoreExtraInput) ? "ignore" : "error");
+ printf(" lockTimeout = %ld; # ms\n", lockTimeout);
+ printf(" readTimeout = %ld; # ms\n", readTimeout);
+ printf(" replyTimeout = %ld; # ms\n", replyTimeout);
+ printf(" writeTimeout = %ld; # ms\n", writeTimeout);
+ printf(" pollPeriod = %ld; # ms\n", pollPeriod);
+ printf(" maxInput = %ld; # bytes\n", maxInput);
+ StreamProtocolParser::printString(buffer.clear(), inTerminator());
+ printf(" inTerminator = \"%s\";\n", buffer());
+ StreamProtocolParser::printString(buffer.clear(), outTerminator());
+ printf(" outTerminator = \"%s\";\n", buffer());
+ StreamProtocolParser::printString(buffer.clear(), separator());
+ printf(" separator = \"%s\";\n", buffer());
+ if (onInit)
+ printf(" @Init {\n%s }\n",
+ printCommands(buffer.clear(), onInit()));
+ if (onReplyTimeout)
+ printf(" @ReplyTimeout {\n%s }\n",
+ printCommands(buffer.clear(), onReplyTimeout()));
+ if (onReadTimeout)
+ printf(" @ReadTimeout {\n%s }\n",
+ printCommands(buffer.clear(), onReadTimeout()));
+ if (onWriteTimeout)
+ printf(" @WriteTimeout {\n%s }\n",
+ printCommands(buffer.clear(), onWriteTimeout()));
+ 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()));
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+StreamCore* StreamCore::first = NULL;
+
+StreamCore::
+StreamCore()
+{
+ businterface = NULL;
+ flags = None;
+ next = NULL;
+ unparsedInput = false;
+ // add myself to list of streams
+ StreamCore** pstream;
+ for (pstream = &first; *pstream; pstream = &(*pstream)->next);
+ *pstream = this;
+}
+
+StreamCore::
+~StreamCore()
+{
+ debug("~StreamCore(%s) %p\n", name(), (void*)this);
+ releaseBus();
+ // remove myself from list of all streams
+ StreamCore** pstream;
+ for (pstream = &first; *pstream; pstream = &(*pstream)->next)
+ {
+ if (*pstream == this)
+ {
+ *pstream = next;
+ break;
+ }
+ }
+}
+
+bool StreamCore::
+attachBus(const char* busname, int addr, const char* param)
+{
+ releaseBus();
+ businterface = StreamBusInterface::find(this, busname, addr, param);
+ if (!businterface)
+ {
+ error("Businterface '%s' not found for '%s'\n",
+ busname, name());
+ return false;
+ }
+ debug("StreamCore::attachBus(busname=\"%s\", addr=%i, param=\"%s\") businterface=%p\n",
+ busname, addr, param, (void*)businterface);
+ return true;
+}
+
+void StreamCore::
+releaseBus()
+{
+ if (businterface)
+ {
+ if (flags & BusOwner)
+ {
+ busUnlock();
+ }
+ busRelease();
+ businterface = NULL;
+ }
+}
+
+// Parse the protocol
+
+bool StreamCore::
+parse(const char* filename, const char* _protocolname)
+{
+ protocolname = _protocolname;
+ // extract substitutions from protocolname "name(sub1,sub2)"
+ int i = protocolname.find('(');
+ if (i >= 0)
+ {
+ while (i >= 0)
+ {
+ protocolname[i] = '\0'; // replace '(' and ',' with '\0'
+ i = protocolname.find(',', i+1);
+ }
+ // should have closing parentheses
+ if (protocolname[-1] != ')')
+ {
+ error("Missing ')' after substitutions '%s'\n", _protocolname);
+ return false;
+ }
+ protocolname.truncate(-1); // remove ')'
+ }
+ StreamProtocolParser::Protocol* protocol;
+ protocol = StreamProtocolParser::getProtocol(filename, protocolname);
+ if (!protocol)
+ {
+ error("while reading protocol '%s' for '%s'\n", protocolname(), name());
+ return false;
+ }
+ if (!compile(protocol))
+ {
+ delete protocol;
+ error("while compiling protocol '%s' for '%s'\n", _protocolname, name());
+ return false;
+ }
+ delete protocol;
+ return true;
+}
+
+bool StreamCore::
+compile(StreamProtocolParser::Protocol* protocol)
+{
+ const char* extraInputNames [] = {"error", "ignore", NULL};
+
+ // default values for protocol variables
+ flags &= ~IgnoreExtraInput;
+ lockTimeout = 5000;
+ readTimeout = 100;
+ replyTimeout = 1000;
+ writeTimeout = 100;
+ maxInput = 0;
+ pollPeriod = 1000;
+ inTerminatorDefined = false;
+ outTerminatorDefined = false;
+
+ unsigned short ignoreExtraInput = false;
+ if (!protocol->getEnumVariable("extrainput", ignoreExtraInput,
+ extraInputNames))
+ {
+ return false;
+ }
+ if (ignoreExtraInput) flags |= IgnoreExtraInput;
+ if (!(protocol->getNumberVariable("locktimeout", lockTimeout) &&
+ protocol->getNumberVariable("readtimeout", readTimeout) &&
+ protocol->getNumberVariable("replytimeout", replyTimeout) &&
+ protocol->getNumberVariable("writetimeout", writeTimeout) &&
+ protocol->getNumberVariable("maxinput", maxInput) &&
+ // use replyTimeout as default for pollPeriod
+ protocol->getNumberVariable("replytimeout", pollPeriod) &&
+ protocol->getNumberVariable("pollperiod", pollPeriod)))
+ {
+ return false;
+ }
+ if (!(protocol->getStringVariable("terminator", inTerminator, &inTerminatorDefined) &&
+ protocol->getStringVariable("terminator", outTerminator, &outTerminatorDefined) &&
+ protocol->getStringVariable("interminator", inTerminator, &inTerminatorDefined) &&
+ protocol->getStringVariable("outterminator", outTerminator, &outTerminatorDefined) &&
+ protocol->getStringVariable("separator", separator)))
+ {
+ return false;
+ }
+ if (!(protocol->getCommands(NULL, commands, this) &&
+ protocol->getCommands("@init", onInit, this) &&
+ protocol->getCommands("@writetimeout", onWriteTimeout, this) &&
+ protocol->getCommands("@replytimeout", onReplyTimeout, this) &&
+ protocol->getCommands("@readtimeout", onReadTimeout, this) &&
+ protocol->getCommands("@mismatch", onMismatch, this)))
+ {
+ return false;
+ }
+ return protocol->checkUnused();
+}
+
+bool StreamCore::
+compileCommand(StreamProtocolParser::Protocol* protocol,
+ StreamBuffer& buffer, const char* command, const char*& args)
+{
+ unsigned long timeout = 0;
+
+ if (strcmp(command, commandStr[in_cmd]) == 0)
+ {
+ buffer.append(in_cmd);
+ if (!protocol->compileString(buffer, args,
+ ScanFormat, this))
+ {
+ return false;
+ }
+ buffer.append(StreamProtocolParser::eos);
+ return true;
+ }
+ if (strcmp(command, commandStr[out_cmd]) == 0)
+ {
+ buffer.append(out_cmd);
+ if (!protocol->compileString(buffer, args,
+ PrintFormat, this))
+ {
+ return false;
+ }
+ buffer.append(StreamProtocolParser::eos);
+ return true;
+ }
+ if (strcmp(command, commandStr[wait_cmd]) == 0)
+ {
+ buffer.append(wait_cmd);
+ if (!protocol->compileNumber(timeout, args))
+ {
+ return false;
+ }
+ buffer.append(&timeout, sizeof(timeout));
+ return true;
+ }
+ if (strcmp(command, commandStr[event_cmd]) == 0)
+ {
+ if (!busSupportsEvent())
+ {
+ protocol->errorMsg(getLineNumber(command),
+ "Events not supported by businterface.\n");
+ return false;
+ }
+ unsigned long eventmask = 0xffffffff;
+ buffer.append(event_cmd);
+ if (*args == '(')
+ {
+ if (!protocol->compileNumber(eventmask, ++args))
+ {
+ return false;
+ }
+ if (*args != ')')
+ {
+ protocol->errorMsg(getLineNumber(command),
+ "Expect ')' instead of: '%s'\n", args);
+ return false;
+ }
+ args++;
+ }
+ buffer.append(&eventmask, sizeof(eventmask));
+ if (*args)
+ {
+ if (!protocol->compileNumber(timeout, args))
+ {
+ return false;
+ }
+ }
+ buffer.append(&timeout, sizeof(timeout));
+ return true;
+ }
+ if (strcmp(command, commandStr[exec_cmd]) == 0)
+ {
+ buffer.append(exec_cmd);
+ if (!protocol->compileString(buffer, args,
+ NoFormat, this))
+ {
+ return false;
+ }
+ buffer.append(StreamProtocolParser::eos);
+ return true;
+ }
+ if (strcmp(command, commandStr[connect_cmd]) == 0)
+ {
+ buffer.append(connect_cmd);
+ if (!protocol->compileNumber(timeout, args))
+ {
+ return false;
+ }
+ buffer.append(&timeout, sizeof(timeout));
+ return true;
+ }
+ if (strcmp(command, commandStr[disconnect_cmd]) == 0)
+ {
+ buffer.append(disconnect_cmd);
+ return true;
+ }
+
+ protocol->errorMsg(getLineNumber(command),
+ "Unknown command name '%s'\n", command);
+ return false;
+}
+
+// Run the protocol
+
+bool StreamCore::
+startProtocol(StartMode startMode)
+{
+ MutexLock lock(this);
+ debug("StreamCore::startProtocol(%s, startMode=%s)\n", name(),
+ startMode == StartNormal ? "StartNormal" :
+ startMode == StartInit ? "StartInit" :
+ startMode == StartAsync ? "StartAsync" : "Invalid");
+ if (!businterface)
+ {
+ error("%s: No businterface attached\n", name());
+ return false;
+ }
+ flags &= ~ClearOnStart;
+ switch (startMode)
+ {
+ case StartInit:
+ flags |= InitRun;
+ break;
+ case StartAsync:
+ if (!busSupportsAsyncRead())
+ {
+ error("%s: Businterface does not support async mode\n", name());
+ return false;
+ }
+ flags |= AsyncMode;
+ break;
+ case StartNormal:
+ break;
+ }
+ if (!commands)
+ {
+ error ("%s: No protocol loaded\n", name());
+ return false;
+ }
+ commandIndex = (startMode == StartInit) ? onInit() : commands();
+ runningHandler = Success;
+ protocolStartHook();
+ return evalCommand();
+}
+
+void StreamCore::
+finishProtocol(ProtocolResult status)
+{
+ if (flags & BusPending)
+ {
+ error("StreamCore::finishProtocol(%s): Still waiting for %s%s%s\n",
+ name(),
+ flags & LockPending ? "lockSuccess() " : "",
+ flags & WritePending ? "writeSuccess() " : "",
+ flags & WaitPending ? "timerCallback()" : "");
+ status = Fault;
+ }
+ flags &= ~(AcceptInput|AcceptEvent);
+ if (runningHandler)
+ {
+ // get original error status
+ if (status == Success) status = runningHandler;
+ }
+ else
+ {
+ // save original error status
+ runningHandler = status;
+ // look for error handler
+ char* handler;
+ switch (status)
+ {
+ case Success:
+ handler = NULL;
+ break;
+ case WriteTimeout:
+ handler = onWriteTimeout();
+ break;
+ case ReplyTimeout:
+ handler = onReplyTimeout();
+ break;
+ case ReadTimeout:
+ handler = onReadTimeout();
+ break;
+ case ScanError:
+ handler = onMismatch();
+ /* reparse old input if first command in handler is 'in' */
+ if (*handler == in_cmd)
+ {
+ debug("reparsing input \"%s\"\n",
+ inputLine.expand()());
+ commandIndex = handler + 1;
+ if (matchInput())
+ {
+ evalCommand();
+ return;
+ }
+ handler = NULL;
+ break;
+ }
+ break;
+ default:
+ // get rid of all the rubbish whe might have collected
+ inputBuffer.clear();
+ handler = NULL;
+ }
+ if (handler)
+ {
+ debug("starting exception handler\n");
+ // execute handler
+ commandIndex = handler;
+ evalCommand();
+ return;
+ }
+ }
+ debug("StreamCore::finishProtocol(%s, status=%s) %sbus owner\n",
+ name(),
+ status==0 ? "Success" :
+ status==1 ? "LockTimeout" :
+ status==2 ? "WriteTimeout" :
+ status==3 ? "ReplyTimeout" :
+ status==4 ? "ReadTimeout" :
+ status==5 ? "ScanError" :
+ status==6 ? "FormatError" :
+ status==7 ? "Abort" :
+ status==8 ? "Fault" : "Invalid",
+ flags & BusOwner ? "" : "not ");
+ if (flags & BusOwner)
+ {
+ busUnlock();
+ flags &= ~BusOwner;
+ }
+ busFinish();
+ protocolFinishHook(status);
+}
+
+bool StreamCore::
+evalCommand()
+{
+ if (flags & BusPending)
+ {
+ error("StreamCore::evalCommand(%s): Still waiting for %s%s%s",
+ name(),
+ flags & LockPending ? "lockSuccess() " : "",
+ flags & WritePending ? "writeSuccess() " : "",
+ flags & WaitPending ? "timerCallback()" : "");
+ return false;
+ }
+ activeCommand = commandIndex;
+ debug("StreamCore::evalCommand(%s): activeCommand = %s\n",
+ name(), commandName(*activeCommand));
+ switch (*commandIndex++)
+ {
+ case out_cmd:
+ flags &= ~(AcceptInput|AcceptEvent);
+ return evalOut();
+ case in_cmd:
+ flags &= ~AcceptEvent;
+ return evalIn();
+ case wait_cmd:
+ flags &= ~(AcceptInput|AcceptEvent);
+ return evalWait();
+ case event_cmd:
+ flags &= ~AcceptInput;
+ return evalEvent();
+ case exec_cmd:
+ return evalExec();
+ case end_cmd:
+ finishProtocol(Success);
+ return true;
+ case connect_cmd:
+ return evalConnect();
+ case disconnect_cmd:
+ return evalDisconnect();
+ default:
+ error("INTERNAL ERROR (%s): illegal command code 0x%02x\n",
+ name(), *activeCommand);
+ flags &= ~BusPending;
+ finishProtocol(Fault);
+ return false;
+ }
+}
+
+// Handle 'out' command
+
+bool StreamCore::
+evalOut()
+{
+ inputBuffer.clear(); // flush all unread input
+ unparsedInput = false;
+ outputLine.clear();
+ if (!formatOutput())
+ {
+ finishProtocol(FormatError);
+ return false;
+ }
+ outputLine.append(outTerminator);
+ debug ("StreamCore::evalOut: outputLine = \"%s\"\n", outputLine.expand()());
+ if (*commandIndex == in_cmd) // prepare for early input
+ {
+ flags |= AcceptInput;
+ }
+ if (*commandIndex == event_cmd) // prepare for early event
+ {
+ flags |= AcceptEvent;
+ }
+ if (!(flags & BusOwner))
+ {
+ debug ("StreamCore::evalOut(%s): lockRequest(%li)\n",
+ name(), flags & InitRun ? 0 : lockTimeout);
+ flags |= LockPending;
+ if (!busLockRequest(flags & InitRun ? 0 : lockTimeout))
+ {
+ return false;
+ }
+ return true;
+ }
+ flags |= WritePending;
+ if (!busWriteRequest(outputLine(), outputLine.length(), writeTimeout))
+ {
+ return false;
+ }
+ return true;
+}
+
+bool StreamCore::
+formatOutput()
+{
+ char command;
+ const char* fieldName = NULL;
+ const char* formatstring;
+ while ((command = *commandIndex++) != StreamProtocolParser::eos)
+ {
+ switch (command)
+ {
+ case StreamProtocolParser::format_field:
+ {
+ debug("StreamCore::formatOutput(%s): StreamProtocolParser::format_field\n",
+ name());
+ // code layout:
+ // field addrlen AddressStructure formatstring StreamFormat [info]
+ fieldName = commandIndex;
+ commandIndex += strlen(commandIndex)+1;
+ unsigned short addrlen = extract(commandIndex);
+ fieldAddress.set(commandIndex, addrlen);
+ commandIndex += addrlen;
+ }
+ case StreamProtocolParser::format:
+ {
+ // code layout:
+ // formatstring StreamFormat [info]
+ formatstring = commandIndex;
+ while (*commandIndex++); // jump after
+ StreamFormat fmt = extract(commandIndex);
+ fmt.info = commandIndex; // point to info string
+ commandIndex += fmt.infolen;
+ debug("StreamCore::formatOutput(%s): format = %%%s\n",
+ name(), formatstring);
+ if (fmt.type == pseudo_format)
+ {
+ if (!StreamFormatConverter::find(fmt.conv)->
+ printPseudo(fmt, outputLine))
+ {
+ error("%s: Can't print pseudo value '%%%s'\n",
+ name(), formatstring);
+ return false;
+ }
+ continue;
+ }
+ flags &= ~Separator;
+ if (!formatValue(fmt, fieldAddress ? fieldAddress() : NULL))
+ {
+ if (fieldName)
+ error("%s: Cannot format field '%s' with '%%%s'\n",
+ name(), fieldName, formatstring);
+ else
+ error("%s: Cannot format value with '%%%s'\n",
+ name(), formatstring);
+ return false;
+ }
+ fieldAddress.clear();
+ fieldName = NULL;
+ continue;
+ }
+ case esc:
+ // escaped literal byte
+ command = *commandIndex++;
+ default:
+ // literal byte
+ outputLine.append(command);
+ }
+ }
+ return true;
+}
+
+void StreamCore::
+printSeparator()
+{
+ if (!(flags & Separator))
+ {
+ flags |= Separator;
+ return;
+ }
+ 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]);
+ }
+}
+
+bool StreamCore::
+printValue(const StreamFormat& fmt, long value)
+{
+ if (fmt.type != long_format && fmt.type != enum_format)
+ {
+ error("%s: printValue(long) called with %%%c format\n",
+ name(), fmt.conv);
+ return false;
+ }
+ printSeparator();
+ if (!StreamFormatConverter::find(fmt.conv)->
+ printLong(fmt, outputLine, value))
+ {
+ error("%s: Formatting value %li failed\n",
+ name(), value);
+ return false;
+ }
+ return true;
+}
+
+bool StreamCore::
+printValue(const StreamFormat& fmt, double value)
+{
+ if (fmt.type != double_format)
+ {
+ error("%s: printValue(double) called with %%%c format\n",
+ name(), fmt.conv);
+ return false;
+ }
+ printSeparator();
+ if (!StreamFormatConverter::find(fmt.conv)->
+ printDouble(fmt, outputLine, value))
+ {
+ error("%s: Formatting value %#g failed\n",
+ name(), value);
+ return false;
+ }
+ return true;
+}
+
+bool StreamCore::
+printValue(const StreamFormat& fmt, char* value)
+{
+ if (fmt.type != string_format)
+ {
+ error("%s: printValue(char*) called with %%%c format\n",
+ name(), fmt.conv);
+ return false;
+ }
+ printSeparator();
+ if (!StreamFormatConverter::find(fmt.conv)->
+ printString(fmt, outputLine, value))
+ {
+ StreamBuffer buffer(value);
+ error("%s: Formatting value \"%s\" failed\n",
+ name(), buffer.expand()());
+ return false;
+ }
+ return true;
+}
+
+void StreamCore::
+lockCallback(StreamIoStatus status)
+{
+ MutexLock lock(this);
+ debug("StreamCore::lockCallback(%s, status=%s)\n",
+ name(), status ? "Timeout" : "Success");
+ if (!(flags & LockPending))
+ {
+ error("StreamCore::lockCallback(%s) called unexpectedly\n",
+ name());
+ return;
+ }
+ flags &= ~LockPending;
+ flags |= BusOwner;
+ if (status != StreamIoSuccess)
+ {
+ finishProtocol(LockTimeout);
+ return;
+ }
+ flags |= WritePending;
+ if (!busWriteRequest(outputLine(), outputLine.length(), writeTimeout))
+ {
+ finishProtocol(Fault);
+ }
+}
+
+void StreamCore::
+writeCallback(StreamIoStatus status)
+{
+ MutexLock lock(this);
+ debug("StreamCore::writeCallback(%s, status=%s)\n",
+ name(), status ? "Timeout" : "Success");
+ if (!(flags & WritePending))
+ {
+ error("StreamCore::writeCallback(%s) called unexpectedly\n",
+ name());
+ return;
+ }
+ flags &= ~WritePending;
+ if (status != StreamIoSuccess)
+ {
+ finishProtocol(WriteTimeout);
+ return;
+ }
+ evalCommand();
+}
+
+const char* StreamCore::
+getOutTerminator(size_t& length)
+{
+ if (outTerminatorDefined)
+ {
+ length = outTerminator.length();
+ return outTerminator();
+ }
+ else
+ {
+ length = 0;
+ return NULL;
+ }
+}
+
+// Handle 'in' command
+
+bool StreamCore::
+evalIn()
+{
+ flags |= AcceptInput;
+ long expectedInput;
+
+ expectedInput = maxInput;
+ if (unparsedInput)
+ {
+ // handle early input
+ debug("StreamCore::evalIn(%s): early input: %s\n",
+ name(), inputBuffer.expand()());
+ expectedInput = readCallback(lastInputStatus, NULL, 0);
+ if (!expectedInput)
+ {
+ // no more input needed
+ return true;
+ }
+ }
+ if (flags & AsyncMode)
+ {
+ // release bus
+ if (flags & BusOwner)
+ {
+ debug("StreamCore::evalIn(%s): unlocking bus\n",
+ name());
+ busUnlock();
+ flags &= ~BusOwner;
+ }
+ busReadRequest(pollPeriod, readTimeout,
+ expectedInput, true);
+ return true;
+ }
+ busReadRequest(replyTimeout, readTimeout,
+ expectedInput, false);
+ // continue with readCallback() in another thread
+ return true;
+}
+
+long StreamCore::
+readCallback(StreamIoStatus status,
+ const void* input, long size)
+// returns number of bytes to read additionally
+
+{
+ if (status < 0 || status > StreamIoFault)
+ {
+ error("StreamCore::readCallback(%s) called with illegal StreamIoStatus %d\n",
+ name(), status);
+ return 0;
+ }
+ MutexLock lock(this);
+ lastInputStatus = status;
+
+#ifndef NO_TEMPORARY
+ debug("StreamCore::readCallback(%s, status=%s input=\"%s\", size=%ld)\n",
+ name(), StreamIoStatusStr[status],
+ StreamBuffer(input, size).expand()(), size);
+#endif
+
+ if (!(flags & AcceptInput))
+ {
+ error("StreamCore::readCallback(%s) called unexpectedly\n",
+ name());
+ return 0;
+ }
+ flags &= ~AcceptInput;
+ unparsedInput = false;
+ switch (status)
+ {
+ case StreamIoTimeout:
+ // timeout is valid end if we have no terminator
+ // and number of input bytes is not limited
+ if (!inTerminator && !maxInput)
+ {
+ status = StreamIoEnd;
+ }
+ // else timeout might be ok if we find a terminator
+ break;
+ case StreamIoSuccess:
+ case StreamIoEnd:
+ break;
+ case StreamIoNoReply:
+ if (flags & AsyncMode)
+ {
+ // just restart in asyn mode
+ debug("StreamCore::readCallback(%s) no async input: just restart\n",
+ name());
+ evalIn();
+ return 0;
+ }
+ error("%s: No reply from device within %ld ms\n",
+ name(), replyTimeout);
+ inputBuffer.clear();
+ finishProtocol(ReplyTimeout);
+ return 0;
+ case StreamIoFault:
+ error("%s: I/O error when reading from device\n", name());
+ finishProtocol(Fault);
+ return 0;
+ }
+ inputBuffer.append(input, size);
+ debug("StreamCore::readCallback(%s) inputBuffer=\"%s\", size %ld\n",
+ name(), inputBuffer.expand()(), inputBuffer.length());
+ if (*activeCommand != in_cmd)
+ {
+ // early input, stop here and wait for in command
+ // -- Should we limit size of inputBuffer? --
+ if (inputBuffer) unparsedInput = true;
+ return 0;
+ }
+
+ // prepare to parse the input
+ const char *commandStart = commandIndex;
+ long end = -1;
+ long termlen = 0;
+
+ 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 ");
+ }
+ if (status == StreamIoEnd && end < 0)
+ {
+ // no terminator but end flag
+ debug("StreamCore::readCallback(%s) end flag received\n",
+ name());
+ end = inputBuffer.length();
+ }
+ if (maxInput && end < 0 && (long)maxInput <= inputBuffer.length())
+ {
+ // no terminator but maxInput bytes read
+ debug("StreamCore::readCallback(%s) maxInput size reached\n",
+ name());
+ end = maxInput;
+ }
+ if (maxInput && end > (long)maxInput)
+ {
+ // limit input length to maxInput (ignore terminator)
+ end = maxInput;
+ termlen = 0;
+ }
+ if (end < 0)
+ {
+ // no end found
+ if (status != StreamIoTimeout)
+ {
+ // input is incomplete - wait for more
+ debug("StreamCore::readCallback(%s) wait for more input\n",
+ name());
+ flags |= AcceptInput;
+ if (maxInput)
+ return maxInput - inputBuffer.length();
+ else
+ return -1;
+ }
+ // try to parse what we got
+ end = inputBuffer.length();
+ if (!(flags & AsyncMode))
+ {
+ 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",
+ name(), inputLine.expand()());
+ bool matches = matchInput();
+ inputBuffer.remove(end + termlen);
+ if (inputBuffer)
+ {
+ debug("StreamCore::readCallback(%s) unpared input left: \"%s\"\n",
+ name(), inputBuffer.expand()());
+ unparsedInput = true;
+ }
+ if (!matches)
+ {
+ if (status == StreamIoTimeout)
+ {
+ // we have not forgotten the timeout
+ finishProtocol(ReadTimeout);
+ return 0;
+ }
+ if (flags & AsyncMode)
+ {
+ debug("StreamCore::readCallback(%s) async match failure: just restart\n",
+ name());
+ commandIndex = commandStart;
+ evalIn();
+ return 0;
+ }
+ debug("StreamCore::readCallback(%s) match failure\n",
+ name());
+ finishProtocol(ScanError);
+ return 0;
+ }
+ if (status == StreamIoTimeout)
+ {
+ // we have not forgotten the timeout
+ finishProtocol(ReadTimeout);
+ return 0;
+ }
+ // end input mode and do next command
+ flags &= ~(AsyncMode|AcceptInput);
+ // -- should we tell someone that input has finished? --
+ evalCommand();
+ return 0;
+}
+
+bool StreamCore::
+matchInput()
+{
+ /* Don't write messages about matching errors if either in asynchronous
+ mode (then we just wait for new matching input) or if @mismatch handler
+ is installed and starts with 'in' (then we reparse the input).
+ */
+ char command;
+ const char* formatstring;
+
+ consumedInput = 0;
+
+ while ((command = *commandIndex++) != StreamProtocolParser::eos)
+ {
+ switch (command)
+ {
+ case StreamProtocolParser::format_field:
+ {
+ // code layout:
+ // field addrlen AddressStructure formatstring StreamFormat [info]
+ commandIndex += strlen(commandIndex)+1;
+ unsigned short addrlen = extract(commandIndex);
+ fieldAddress.set(commandIndex, addrlen);
+ commandIndex += addrlen;
+ }
+ case StreamProtocolParser::format:
+ {
+ int consumed;
+ // code layout:
+ // formatstring StreamFormat [info]
+ formatstring = commandIndex;
+ while (*commandIndex++ != StreamProtocolParser::eos); // jump after
+
+ StreamFormat fmt = extract(commandIndex);
+ fmt.info = commandIndex;
+ commandIndex += fmt.infolen;
+ if (fmt.flags & skip_flag || fmt.type == pseudo_format)
+ {
+ long ldummy;
+ double ddummy;
+ switch (fmt.type)
+ {
+ case long_format:
+ case enum_format:
+ consumed = StreamFormatConverter::find(fmt.conv)->
+ scanLong(fmt, inputLine(consumedInput), ldummy);
+ break;
+ case double_format:
+ consumed = StreamFormatConverter::find(fmt.conv)->
+ scanDouble(fmt, inputLine(consumedInput), ddummy);
+ break;
+ case string_format:
+ consumed = StreamFormatConverter::find(fmt.conv)->
+ scanString(fmt, inputLine(consumedInput), NULL, 0);
+ break;
+ case pseudo_format:
+ // pass complete input
+ consumed = StreamFormatConverter::find(fmt.conv)->
+ scanPseudo(fmt, inputLine, consumedInput);
+ break;
+ default:
+ error("INTERNAL ERROR (%s): illegal format.type 0x%02x\n",
+ name(), fmt.type);
+ return false;
+ }
+ if (consumed < 0)
+ {
+ 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;
+ }
+ flags &= ~Separator;
+ if (!matchValue(fmt, fieldAddress ? fieldAddress() : NULL))
+ {
+ if (!(flags & AsyncMode) && onMismatch[0] != in_cmd)
+ {
+ if (flags & ScanTried)
+ error("%s: Input \"%s%s\" does not match format %%%s\n",
+ name(), inputLine.expand(consumedInput, 20)(),
+ inputLine.length()-consumedInput > 20 ? "..." : "",
+ formatstring);
+ else
+ error("%s: Can't scan value with format %%%s\n",
+ name(), formatstring);
+ }
+ return false;
+ }
+ // matchValue() has already removed consumed bytes from inputBuffer
+ fieldAddress = NULL;
+ break;
+ }
+ case StreamProtocolParser::skip:
+ // ignore next input byte
+ consumedInput++;
+ break;
+ case esc:
+ // escaped literal byte
+ command = *commandIndex++;
+ default:
+ // literal byte
+ if (consumedInput >= inputLine.length())
+ {
+ int i = 0;
+ while (commandIndex[i] >= ' ') i++;
+ if (!(flags & AsyncMode) && onMismatch[0] != in_cmd)
+ {
+ error("%s: Input \"%s%s\" too short."
+ " No match for \"%s\"\n",
+ name(),
+ inputLine.length() > 20 ? "..." : "",
+ inputLine.expand(-20)(),
+ StreamBuffer(commandIndex-1,i+1).expand()());
+ }
+ return false;
+ }
+ if (command != inputLine[consumedInput])
+ {
+ if (!(flags & AsyncMode) && onMismatch[0] != in_cmd)
+ {
+ int i = 0;
+ while (commandIndex[i] >= ' ') i++;
+ error("%s: Input \"%s%s\" mismatch after %ld byte%s\n",
+ name(),
+ consumedInput > 10 ? "..." : "",
+ inputLine.expand(consumedInput > 10 ?
+ consumedInput-10 : 0,20)(),
+ consumedInput,
+ consumedInput==1 ? "" : "s");
+
+ error("%s: got \"%s\" where \"%s\" was expected\n",
+ name(),
+ inputLine.expand(consumedInput, 20)(),
+ StreamBuffer(commandIndex-1,i+1).expand()());
+ }
+ return false;
+ }
+ consumedInput++;
+ }
+ }
+ long surplus = inputLine.length()-consumedInput;
+ if (surplus > 0 && !(flags & IgnoreExtraInput))
+ {
+ if (!(flags & AsyncMode) && onMismatch[0] != in_cmd)
+ {
+ error("%s: %ld byte%s surplus input \"%s%s\"\n",
+ name(), surplus, surplus==1 ? "" : "s",
+ inputLine.expand(consumedInput, 20)(),
+ surplus > 20 ? "..." : "");
+
+ if (consumedInput>20)
+ error("%s: after %ld byte%s \"...%s\"\n",
+ name(), consumedInput,
+ consumedInput==1 ? "" : "s",
+ inputLine.expand(consumedInput-20, 20)());
+ else
+ error("%s: after %ld byte%s: \"%s\"\n",
+ name(), consumedInput,
+ consumedInput==1 ? "" : "s",
+ inputLine.expand(0, consumedInput)());
+ }
+ return false;
+ }
+ return true;
+}
+
+bool StreamCore::
+matchSeparator()
+{
+ if (!(flags & Separator))
+ {
+ flags |= Separator;
+ return true;
+ }
+ if (!separator) return true;
+ long i = 0;
+ if (separator[0] == ' ')
+ {
+ i++;
+ // skip leading whitespaces
+ while (isspace(inputLine[consumedInput++]));
+ }
+ 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;
+ return true;
+}
+
+long StreamCore::
+scanValue(const StreamFormat& fmt, long& value)
+{
+ if (fmt.type != long_format && fmt.type != enum_format)
+ {
+ error("%s: scanValue(long&) called with %%%c format\n",
+ name(), fmt.conv);
+ return -1;
+ }
+ flags |= ScanTried;
+ if (!matchSeparator()) return -1;
+ long consumed = StreamFormatConverter::find(fmt.conv)->
+ 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;
+ debug("StreamCore::scanValue(%s) scanned %li\n",
+ name(), value);
+ flags |= GotValue;
+ return consumed;
+}
+
+long StreamCore::
+scanValue(const StreamFormat& fmt, double& value)
+{
+ if (fmt.type != double_format)
+ {
+ error("%s: scanValue(double&) called with %%%c format\n",
+ name(), fmt.conv);
+ return -1;
+ }
+ flags |= ScanTried;
+ if (!matchSeparator()) return -1;
+ 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;
+ debug("StreamCore::scanValue(%s) scanned %#g\n",
+ name(), value);
+ flags |= GotValue;
+ return consumed;
+}
+
+long StreamCore::
+scanValue(const StreamFormat& fmt, char* value, long maxlen)
+{
+ if (fmt.type != string_format)
+ {
+ error("%s: scanValue(char*) called with %%%c format\n",
+ name(), fmt.conv);
+ return -1;
+ }
+ if (maxlen < 0) maxlen = 0;
+ flags |= ScanTried;
+ if (!matchSeparator()) return -1;
+ long consumed = StreamFormatConverter::find(fmt.conv)->
+ 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;
+#ifndef NO_TEMPORARY
+ debug("StreamCore::scanValue(%s) scanned \"%s\"\n",
+ name(), StreamBuffer(value, consumed).expand()());
+#endif
+ flags |= GotValue;
+ return consumed;
+}
+
+const char* StreamCore::
+getInTerminator(size_t& length)
+{
+ if (inTerminatorDefined)
+ {
+ length = inTerminator.length();
+ return inTerminator();
+ }
+ else
+ {
+ length = 0;
+ return NULL;
+ }
+}
+
+// Handle 'event' command
+
+bool StreamCore::
+evalEvent()
+{
+ // code layout:
+ // eventmask timeout
+ unsigned long eventMask = extract(commandIndex);
+ unsigned long eventTimeout = extract(commandIndex);
+ if (flags & AsyncMode && eventTimeout == 0)
+ {
+ if (flags & BusOwner)
+ {
+ busUnlock();
+ flags &= ~BusOwner;
+ }
+ }
+ flags |= AcceptEvent;
+ busAcceptEvent(eventMask, eventTimeout);
+ return true;
+}
+
+void StreamCore::
+eventCallback(StreamIoStatus status)
+{
+ if (status < 0 || status > StreamIoFault)
+ {
+ error("StreamCore::eventCallback(%s) called with illegal StreamIoStatus %d\n",
+ name(), status);
+ return;
+ }
+ if (!(flags & AcceptEvent))
+ {
+ error("StreamCore::eventCallback(%s) called unexpectedly\n",
+ name());
+ return;
+ }
+ debug("StreamCore::eventCallback(%s, status=%s)\n",
+ name(), StreamIoStatusStr[status]);
+ MutexLock lock(this);
+ flags &= ~AcceptEvent;
+ switch (status)
+ {
+ case StreamIoTimeout:
+ error("%s: No event from device\n", name());
+ finishProtocol(ReplyTimeout);
+ return;
+ case StreamIoSuccess:
+ evalCommand();
+ return;
+ default:
+ error("%s: Event error from device: %s\n",
+ name(), StreamIoStatusStr[status]);
+ finishProtocol(Fault);
+ return;
+ }
+}
+
+// Handle 'wait' command
+
+bool StreamCore::
+evalWait()
+{
+ unsigned long waitTimeout = extract(commandIndex);
+ flags |= WaitPending;
+ startTimer(waitTimeout);
+ return true;
+}
+
+void StreamCore::
+timerCallback()
+{
+ MutexLock lock(this);
+ debug ("StreamCore::timerCallback(%s)\n", name());
+ if (!(flags & WaitPending))
+ {
+ error("StreamCore::timerCallback(%s) called unexpectedly\n",
+ name());
+ return;
+ }
+ flags &= ~WaitPending;
+ evalCommand();
+}
+
+
+bool StreamCore::
+evalExec()
+{
+ outputLine.clear();
+ formatOutput();
+ // release bus
+ if (flags & BusOwner)
+ {
+ debug("StreamCore::evalExec(%s): unlocking bus\n",
+ name());
+ busUnlock();
+ flags &= ~BusOwner;
+ }
+ if (!execute())
+ {
+ error("%s: executing command \"%s\"\n", name(), outputLine());
+ return false;
+ }
+ return true;
+}
+
+void StreamCore::
+execCallback(StreamIoStatus status)
+{
+ switch (status)
+ {
+ case StreamIoSuccess:
+ evalCommand();
+ return;
+ default:
+ error("%s: Shell command \"%s\" failed\n",
+ name(), outputLine());
+ finishProtocol(Fault);
+ return;
+ }
+}
+
+bool StreamCore::execute()
+{
+ error("%s: Command 'exec' not implemented on this system\n",
+ name());
+ return false;
+}
+
+bool StreamCore::evalConnect()
+{
+ unsigned long connectTimeout = extract(commandIndex);
+ if (!busConnectRequest(connectTimeout))
+ {
+ error("%s: Connect not supported for this bus\n",
+ name());
+ return false;
+ }
+ return true;
+}
+
+void StreamCore::
+connectCallback(StreamIoStatus status)
+{
+ switch (status)
+ {
+ case StreamIoSuccess:
+ evalCommand();
+ return;
+ default:
+ error("%s: Connect failed\n",
+ name());
+ finishProtocol(Fault);
+ return;
+ }
+}
+
+bool StreamCore::evalDisconnect()
+{
+ if (!busDisconnect())
+ {
+ error("%s: Cannot disconnect from this bus\n",
+ name());
+ finishProtocol(Fault);
+ return false;
+ }
+ evalCommand();
+ return true;
+}
+
+#include "streamReferences"
diff --git a/src/StreamCore.h b/src/StreamCore.h
new file mode 100644
index 0000000..2fe078c
--- /dev/null
+++ b/src/StreamCore.h
@@ -0,0 +1,224 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is the kernel 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. *
+* *
+***************************************************************/
+
+#ifndef StreamCore_h
+#define StreamCore_h
+
+#include "StreamProtocol.h"
+#include "StreamFormatConverter.h"
+#include "StreamBusInterface.h"
+
+/**************************************
+ virtual methods:
+
+bool getFieldAddress(const char* fieldname, StreamBuffer& address)
+ If a format sting contains a field name (like "%(EGU)s") a value should
+ be taken from / put to that field instead of the default location.
+ This function must convert the fieldname into some address structure and
+ put the structure into the address buffer (e.g. address.set(addressStruct)).
+ The address structure must be suitable for bytewise copying. I.e. memcpy()
+ to so some other memory location must not invalidate its contents. No
+ constructor, destructor or copy operator will be called.
+ formatValue() and matchValue() get a pointer to the structure.
+ getFieldAddress() must return true on success and false on failure.
+
+bool formatValue(const StreamFormat& format, const void* fieldaddress)
+ This function should first get a value from fieldaddress (if not NULL) or
+ the default location (which may depend on format.type).
+ The printValue(format,XXX) function suitable for format.type should be
+ called to print value. If value is an array, printValue() should be called
+ for each element. The separator string will be added automatically.
+ formatValue() must return true on success and false on failure.
+
+bool matchValue(const StreamFormat& format, const void* fieldaddress)
+ This function should first call the scanValue(format,XXX) function
+ suitable for format.type. If scanValue() returns true, the scanned value
+ should be put into fieldaddress (if not NULL) or the default location
+ (which may depend on format.type).
+ If value is an array, scanValue() should be called for each element. It
+ returns false if there is no more element available. The separator string
+ is matched automatically.
+ matchValue() must return true on success and false on failure.
+
+
+void protocolStartHook()
+void protocolFinishHook(ProtocolResult)
+void startTimer(unsigned short timeout)
+void lockRequest(unsigned short timeout)
+void unlock()
+void writeRequest(unsigned short timeout)
+void readRequest(unsigned short replytimeout, unsigned short timeout)
+void readAsyn(unsigned short timeout)
+void acceptEvent(unsigned short mask, unsigned short timeout)
+
+***************************************/
+
+enum Flags {
+ // 0x00FFFFFF reserved for StreamCore
+ None = 0x0000,
+ IgnoreExtraInput = 0x0001,
+ InitRun = 0x0002,
+ AsyncMode = 0x0004,
+ GotValue = 0x0008,
+ BusOwner = 0x0010,
+ Separator = 0x0020,
+ ScanTried = 0x0040,
+ AcceptInput = 0x0100,
+ AcceptEvent = 0x0200,
+ LockPending = 0x0400,
+ WritePending = 0x0800,
+ WaitPending = 0x1000,
+ BusPending = LockPending|WritePending|WaitPending,
+ ClearOnStart = InitRun|AsyncMode|GotValue|BusOwner|Separator|ScanTried|
+ AcceptInput|AcceptEvent|BusPending
+};
+
+struct StreamFormat;
+
+class StreamCore :
+ StreamProtocolParser::Client,
+ StreamBusInterface::Client
+{
+protected:
+ enum ProtocolResult {
+ Success, LockTimeout, WriteTimeout, ReplyTimeout, ReadTimeout,
+ ScanError, FormatError, Abort, Fault
+ };
+
+ enum StartMode {
+ StartNormal, StartInit, StartAsync
+ };
+
+ class MutexLock
+ {
+ StreamCore* stream;
+
+ public:
+ MutexLock(StreamCore* _stream) : stream(_stream)
+ { _stream->lockMutex(); }
+ ~MutexLock()
+ { stream->releaseMutex(); }
+ };
+
+ friend class MutexLock;
+
+ StreamCore* next;
+ static StreamCore* first;
+
+ char* streamname;
+ unsigned long flags;
+
+ bool attachBus(const char* busname, int addr, const char* param);
+ void releaseBus();
+
+ bool startProtocol(StartMode);
+ void finishProtocol(ProtocolResult);
+ void timerCallback();
+
+ bool printValue(const StreamFormat& format, long value);
+ bool printValue(const StreamFormat& format, double value);
+ bool printValue(const StreamFormat& format, char* value);
+ long scanValue(const StreamFormat& format, long& value);
+ 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;
+ unsigned long writeTimeout;
+ unsigned long replyTimeout;
+ unsigned long readTimeout;
+ unsigned long pollPeriod;
+ unsigned long maxInput;
+ bool inTerminatorDefined;
+ bool outTerminatorDefined;
+ StreamBuffer inTerminator;
+ StreamBuffer outTerminator;
+ StreamBuffer separator;
+ StreamBuffer commands; // the normal protocol
+ StreamBuffer onInit; // init protocol (optional)
+ StreamBuffer onWriteTimeout; // error handler (optional)
+ StreamBuffer onReplyTimeout; // error handler (optional)
+ StreamBuffer onReadTimeout; // error handler (optional)
+ StreamBuffer onMismatch; // error handler (optional)
+ const char* commandIndex; // current position
+ const char* activeCommand; // start of current command
+ StreamBuffer outputLine;
+ StreamBuffer inputBuffer;
+ StreamBuffer inputLine;
+ long consumedInput;
+ ProtocolResult runningHandler;
+ StreamBuffer fieldAddress;
+
+ StreamIoStatus lastInputStatus;
+ bool unparsedInput;
+
+ StreamCore(const StreamCore&); // undefined
+ bool compile(StreamProtocolParser::Protocol*);
+ bool evalCommand();
+ bool evalOut();
+ bool evalIn();
+ bool evalEvent();
+ bool evalWait();
+ bool evalExec();
+ bool evalConnect();
+ bool evalDisconnect();
+ bool formatOutput();
+ bool matchInput();
+ bool matchSeparator();
+ void printSeparator();
+
+// StreamProtocolParser::Client methods
+ bool compileCommand(StreamProtocolParser::Protocol*,
+ StreamBuffer&, const char* command, const char*& args);
+ bool getFieldAddress(const char* fieldname,
+ StreamBuffer& address) = 0;
+
+// StreamBusInterface::Client methods
+ void lockCallback(StreamIoStatus status);
+ void writeCallback(StreamIoStatus status);
+ long readCallback(StreamIoStatus status,
+ const void* input, long size);
+ void eventCallback(StreamIoStatus status);
+ void execCallback(StreamIoStatus status);
+ void connectCallback(StreamIoStatus status);
+ const char* getInTerminator(size_t& length);
+ const char* getOutTerminator(size_t& length);
+
+// virtual methods
+ virtual void protocolStartHook() {}
+ virtual void protocolFinishHook(ProtocolResult) {}
+ virtual void startTimer(unsigned long timeout) = 0;
+ virtual bool formatValue(const StreamFormat&, const void* fieldaddress) = 0;
+ virtual bool matchValue (const StreamFormat&, const void* fieldaddress) = 0;
+ virtual void lockMutex() = 0;
+ virtual void releaseMutex() = 0;
+ virtual bool execute();
+
+public:
+ StreamCore();
+ virtual ~StreamCore();
+ bool parse(const char* filename, const char* protocolname);
+ void printProtocol();
+ const char* name() { return streamname; }
+};
+
+#endif
diff --git a/src/StreamEpics.cc b/src/StreamEpics.cc
new file mode 100644
index 0000000..2d914e3
--- /dev/null
+++ b/src/StreamEpics.cc
@@ -0,0 +1,1218 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is the interface to EPICS 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 "StreamCore.h"
+#include "StreamError.h"
+#include "devStream.h"
+
+#ifndef EPICS_3_14
+extern "C" {
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifndef EPICS_3_14
+
+#include
+#include
+
+extern DBBASE *pdbbase;
+
+} // extern "C"
+
+#else
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if EPICS_MODIFICATION<9
+extern "C" {
+// iocshCmd() is missing in iocsh.h (up to R3.14.8.2)
+// To build with win32-x86, you MUST fix iocsh.h.
+// Move the declaration below to iocsh.h and rebuild base.
+epicsShareFunc int epicsShareAPI iocshCmd(const char *command);
+}
+#endif
+
+#include
+
+#endif
+
+#if defined(__vxworks) || defined(vxWorks)
+#include
+#include
+#endif
+
+enum MoreFlags {
+ // 0x00FFFFFF used by StreamCore
+ InDestructor = 0x0100000,
+ ValueReceived = 0x0200000
+};
+
+extern "C" void streamExecuteCommand(CALLBACK *pcallback);
+extern "C" long streamReload(char* recordname);
+
+class Stream : protected StreamCore
+#ifdef EPICS_3_14
+ , epicsTimerNotify
+#endif
+{
+ dbCommon* record;
+ struct link *ioLink;
+ streamIoFunction readData;
+ streamIoFunction writeData;
+#ifdef EPICS_3_14
+ epicsTimerQueueActive* timerQueue;
+ epicsTimer* timer;
+ epicsMutex mutex;
+ epicsEvent initDone;
+#else
+ WDOG_ID timer;
+ CALLBACK timeoutCallback;
+ SEM_ID mutex;
+ SEM_ID initDone;
+#endif
+ StreamBuffer fieldBuffer;
+ int status;
+ int convert;
+ long currentValueLength;
+ IOSCANPVT ioscanpvt;
+ CALLBACK commandCallback;
+
+
+#ifdef EPICS_3_14
+// epicsTimerNotify method
+ expireStatus expire(const epicsTime&);
+#else
+ static void expire(CALLBACK *pcallback);
+#endif
+
+// StreamCore methods
+ // void protocolStartHook(); // Nothing to do here?
+ void protocolFinishHook(ProtocolResult);
+ void startTimer(unsigned long timeout);
+ bool getFieldAddress(const char* fieldname,
+ StreamBuffer& address);
+ bool formatValue(const StreamFormat&,
+ const void* fieldaddress);
+ bool matchValue(const StreamFormat&,
+ const void* fieldaddress);
+ void lockMutex();
+ void releaseMutex();
+ bool execute();
+ friend void streamExecuteCommand(CALLBACK *pcallback);
+
+// Stream Epics methods
+ long initRecord();
+ Stream(dbCommon* record, struct link *ioLink,
+ streamIoFunction readData, streamIoFunction writeData);
+ ~Stream();
+ 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,
+ streamIoFunction readData, streamIoFunction writeData);
+ friend long streamReadWrite(dbCommon *record);
+ friend long streamGetIointInfo(int cmd, dbCommon *record,
+ IOSCANPVT *ppvt);
+ 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:
+ long priority() { return record->prio; };
+ static long report(int interest);
+ static long drvInit();
+};
+
+
+// shell functions ///////////////////////////////////////////////////////
+#ifdef EPICS_3_14
+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()
+{
+ return streamReload(NULL);
+}
+
+extern "C" long streamReload(char* recordname)
+{
+ DBENTRY dbentry;
+ dbCommon* record;
+ long status;
+
+ if(!pdbbase) {
+ error("No database has been loaded\n");
+ return ERROR;
+ }
+ debug("streamReload(%s)\n", recordname);
+ dbInitEntry(pdbbase,&dbentry);
+ for (status = dbFirstRecordType(&dbentry); status == OK;
+ status = dbNextRecordType(&dbentry))
+ {
+ for (status = dbFirstRecord(&dbentry); status == OK;
+ status = dbNextRecord(&dbentry))
+ {
+ char* value;
+ if (dbFindField(&dbentry, "DTYP") != OK)
+ continue;
+ if ((value = dbGetString(&dbentry)) == NULL)
+ continue;
+ if (strcmp(value, "stream") != 0)
+ continue;
+ record=(dbCommon*)dbentry.precnode->precord;
+ if (recordname && strcmp(recordname, record->name) != 0)
+ continue;
+
+ // This cancels any running protocol and reloads
+ // the protocol file
+ status = record->dset->init_record(record);
+ if (status == OK || status == DO_NOT_CONVERT)
+ {
+ printf("%s: Protocol reloaded\n", record->name);
+ }
+ else
+ {
+ error("%s: Protocol reload failed\n", record->name);
+ }
+ }
+ }
+ dbFinishEntry(&dbentry);
+ StreamProtocolParser::free();
+ return OK;
+}
+
+#ifdef EPICS_3_14
+static const iocshArg streamReloadArg0 =
+ { "recordname", iocshArgString };
+static const iocshArg * const streamReloadArgs[] =
+ { &streamReloadArg0 };
+static const iocshFuncDef reloadDef =
+ { "streamReload", 1, streamReloadArgs };
+
+extern "C" void streamReloadFunc (const iocshArgBuf *args)
+{
+ streamReload(args[0].sval);
+}
+
+static void streamRegistrar ()
+{
+#ifdef MEMGUARD
+ iocshRegister(&memguardReportDef, memguardReportFunc);
+#endif
+ iocshRegister(&reloadDef, streamReloadFunc);
+ // make streamReload available for subroutine records
+ registryFunctionAdd("streamReload",
+ (REGISTRYFUNCTION)streamReloadSub);
+ registryFunctionAdd("streamReloadSub",
+ (REGISTRYFUNCTION)streamReloadSub);
+}
+
+extern "C" {
+epicsExportRegistrar(streamRegistrar);
+}
+#endif // EPICS_3_14
+
+// driver support ////////////////////////////////////////////////////////
+
+struct stream_drvsup {
+ long number;
+ long (*report)(int);
+ DRVSUPFUN init;
+} stream = {
+ 2,
+ Stream::report,
+ Stream::drvInit
+};
+
+#ifdef EPICS_3_14
+extern "C" {
+epicsExportAddress(drvet, stream);
+}
+
+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)
+{
+ char* c;
+ TS_STAMP tm;
+ tsLocalTime (&tm);
+ tsStampToText(&tm, TS_TEXT_MMDDYY, buffer);
+ c = strchr(buffer,'.');
+ if (c) {
+ c[4] = 0;
+ }
+}
+#endif
+
+long Stream::
+report(int interest)
+{
+ debug("Stream::report(interest=%d)\n", interest);
+ printf(" %s\n", StreamVersion);
+
+ printf(" registered bus interfaces:\n");
+ StreamBusInterfaceClass interface;
+ while (interface)
+ {
+ printf(" %s\n", interface.name());
+ ++interface;
+ }
+
+ if (interest < 1) return OK;
+ printf(" registered converters:\n");
+ StreamFormatConverter* converter;
+ int c;
+ for (c=0; c < 256; c++)
+ {
+ converter = StreamFormatConverter::find(c);
+ if (converter)
+ {
+ printf(" %%%c %s\n", c, converter->name());
+ }
+ }
+
+ Stream* pstream;
+ printf(" connected records:\n");
+ for (pstream = static_cast(first); pstream;
+ pstream = static_cast(pstream->next))
+ {
+ if (interest == 2)
+ {
+ printf("\n%s: %s\n", pstream->name(),
+ pstream->ioLink->value.instio.string);
+ pstream->printProtocol();
+ }
+ else
+ {
+ printf(" %s: %s\n", pstream->name(),
+ pstream->ioLink->value.instio.string);
+ }
+ }
+ StreamPrintTimestampFunction = streamEpicsPrintTimestamp;
+ return OK;
+}
+
+long Stream::
+drvInit()
+{
+ char* path;
+ debug("drvStreamInit()\n");
+ path = getenv("STREAM_PROTOCOL_PATH");
+#if defined(__vxworks) || defined(vxWorks)
+ // for compatibility reasons look for global symbols
+ if (!path)
+ {
+ char* symbol;
+ SYM_TYPE type;
+ if (symFindByName(sysSymTbl,
+ "STREAM_PROTOCOL_PATH", &symbol, &type) == OK)
+ {
+ path = *(char**)symbol;
+ }
+ else
+ if (symFindByName(sysSymTbl,
+ "STREAM_PROTOCOL_DIR", &symbol, &type) == OK)
+ {
+ path = *(char**)symbol;
+ }
+ }
+#endif
+ if (!path)
+ {
+ fprintf(stderr,
+ "drvStreamInit: Warning! STREAM_PROTOCOL_PATH not set. "
+ "Defaults to \"%s\"\n", StreamProtocolParser::path);
+ }
+ else
+ {
+ StreamProtocolParser::path = path;
+ }
+ debug("StreamProtocolParser::path = %s\n",
+ StreamProtocolParser::path);
+ StreamPrintTimestampFunction = streamEpicsPrintTimestamp;
+ return OK;
+}
+
+// device support (C interface) //////////////////////////////////////////
+
+long streamInit(int after)
+{
+ if (after)
+ {
+ StreamProtocolParser::free();
+ }
+ return OK;
+}
+
+long streamInitRecord(dbCommon* record, struct link *ioLink,
+ streamIoFunction readData, streamIoFunction writeData)
+{
+ debug("streamInitRecord(%s): SEVR=%d\n", record->name, record->sevr);
+ Stream* pstream = (Stream*)record->dpvt;
+ if (!pstream)
+ {
+ // initialize the first time
+ pstream = new Stream(record, ioLink, readData, writeData);
+ record->dpvt = pstream;
+ } else {
+ // stop any running protocol
+ pstream->finishProtocol(Stream::Abort);
+ }
+ // (re)initialize bus and protocol
+ long status = pstream->initRecord();
+ if (status != OK && status != DO_NOT_CONVERT)
+ {
+ error("%s: Record initialization failed\n", record->name);
+ }
+ if (!pstream->ioscanpvt)
+ {
+ scanIoInit(&pstream->ioscanpvt);
+ }
+ return status;
+}
+
+long streamReadWrite(dbCommon *record)
+{
+ Stream* pstream = (Stream*)record->dpvt;
+ if (!pstream || pstream->status == ERROR)
+ {
+ (void) recGblSetSevr(record, UDF_ALARM, INVALID_ALARM);
+ error("%s: Record not initialised correctly\n", record->name);
+ return ERROR;
+ }
+ return pstream->process() ? pstream->convert : ERROR;
+}
+
+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);
+ if (!pstream)
+ {
+ error("streamGetIointInfo called without stream instance\n");
+ return ERROR;
+ }
+ *ppvt = pstream->ioscanpvt;
+ if (cmd == 0)
+ {
+ debug("streamGetIointInfo: starting protocol\n");
+ // SCAN has been set to "I/O Intr"
+ if (!pstream->startProtocol(Stream::StartAsync))
+ {
+ error("%s: Can't start \"I/O Intr\" protocol\n",
+ record->name);
+ }
+ }
+ else
+ {
+ // SCAN is no longer "I/O Intr"
+ pstream->finishProtocol(Stream::Abort);
+ }
+ return OK;
+}
+
+long streamPrintf(dbCommon *record, format_t *format, ...)
+{
+ debug("streamPrintf(%s,format=%%%c)\n",
+ record->name, format->priv->conv);
+ Stream* pstream = (Stream*)record->dpvt;
+ if (!pstream) return ERROR;
+ va_list ap;
+ va_start(ap, format);
+ bool success = pstream->print(format, ap);
+ va_end(ap);
+ 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);
+ Stream* pstream = (Stream*)record->dpvt;
+ if (!pstream) return ERROR;
+ if (!pstream->scan(format, value, maxStringSize))
+ {
+ return ERROR;
+ }
+#ifndef NO_TEMPORARY
+ debug("streamScanfN(%s) success, value=\"%s\"\n",
+ record->name, StreamBuffer((char*)value).expand()());
+#endif
+ return OK;
+}
+
+// Stream methods ////////////////////////////////////////////////////////
+
+Stream::
+Stream(dbCommon* _record, 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
+ timer = wdCreate();
+ mutex = semMCreate(SEM_INVERSION_SAFE | SEM_Q_PRIORITY);
+ initDone = semBCreate(SEM_Q_FIFO, SEM_EMPTY);
+ callbackSetCallback(expire, &timeoutCallback);
+ callbackSetUser(this, &timeoutCallback);
+#endif
+ callbackSetCallback(streamExecuteCommand, &commandCallback);
+ callbackSetUser(this, &commandCallback);
+ status = ERROR;
+ convert = DO_NOT_CONVERT;
+ ioscanpvt = NULL;
+}
+
+Stream::
+~Stream()
+{
+ lockMutex();
+ flags |= InDestructor;;
+ debug("~Stream(%s) %p\n", name(), (void*)this);
+ if (record->dpvt)
+ {
+ finishProtocol(Abort);
+ debug("~Stream(%s): protocol finished\n", name());
+ record->dpvt = NULL;
+ debug("~Stream(%s): dpvt cleared\n", name());
+ }
+#ifdef EPICS_3_14
+ 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()
+{
+ // 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];
+ int n;
+
+ if (ioLink->type != INST_IO)
+ {
+ error("%s: Wrong 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);
+ if (items <= 0)
+ {
+ error("%s: Empty link. Forgot the leading '@' or confused INP with OUT ?\n",
+ name());
+ return S_dev_badInitRet;
+ }
+ if (items < 3)
+ {
+ error("%s: Wrong 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);
+
+ // attach to bus interface
+ if (!attachBus(busname, addr, busparam))
+ {
+ error("%s: Can't attach to bus %s %d\n",
+ name(), busname, addr);
+ return S_dev_noDevice;
+ }
+
+ // parse protocol file
+ if (!parse(filename, protocol))
+ {
+ error("%s: Protocol parse error\n",
+ name());
+ return S_dev_noDevice;
+ }
+
+ // record is ready to use
+ status = NO_ALARM;
+
+ if (ioscanpvt)
+ {
+ // we have been called by streamReload
+ debug("Stream::initRecord %s: initialize after streamReload\n",
+ name());
+ if (record->scan == SCAN_IO_EVENT)
+ {
+ debug("Stream::initRecord %s: restarting \"I/O Intr\" after reload\n",
+ name());
+ if (!startProtocol(StartAsync))
+ {
+ error("%s: Can't restart \"I/O Intr\" protocol\n",
+ name());
+ }
+ }
+ return OK;
+ }
+
+ debug("Stream::initRecord %s: initialize the first time\n",
+ name());
+
+ if (!onInit) return DO_NOT_CONVERT; // no @init handler, keep DOL
+
+ // initialize the record from hardware
+ if (!startProtocol(StartInit))
+ {
+ error("%s: Can't start init run\n",
+ name());
+ return ERROR;
+ }
+ debug("Stream::initRecord %s: waiting for initDone\n",
+ name());
+#ifdef EPICS_3_14
+ initDone.wait();
+#else
+ semTake(initDone, WAIT_FOREVER);
+#endif
+ debug("Stream::initRecord %s: initDone\n",
+ name());
+
+ // init run has set status and convert
+ if (status != NO_ALARM)
+ {
+ record->stat = status;
+ error("%s: @init handler failed\n",
+ name());
+ return ERROR;
+ }
+ debug("Stream::initRecord %s: initialized. convert=%d\n",
+ name(), convert);
+ return convert;
+}
+
+bool Stream::
+process()
+{
+ MutexLock lock(this);
+ if (record->pact || record->scan == SCAN_IO_EVENT)
+ {
+ if (status != NO_ALARM)
+ {
+ debug("Stream::process(%s) error status=%d\n",
+ name(), status);
+ (void) recGblSetSevr(record, status, INVALID_ALARM);
+ return false;
+ }
+ debug("Stream::process(%s) ready. %s\n",
+ name(), convert==2 ?
+ "convert" : "don't convert");
+ return true;
+ }
+ if (flags & InDestructor)
+ {
+ error("%s: Try to process while in stream destructor (try later)\n",
+ name());
+ (void) recGblSetSevr(record, UDF_ALARM, INVALID_ALARM);
+ return false;
+ }
+ debug("Stream::process(%s) start\n", name());
+ status = NO_ALARM;
+ convert = OK;
+ record->pact = true;
+ if (!startProtocol(StreamCore::StartNormal))
+ {
+ debug("Stream::process(%s): could not start, status=%d\n",
+ name(), status);
+ (void) recGblSetSevr(record, status, INVALID_ALARM);
+ return false;
+ }
+ debug("Stream::process(%s): protocol started\n", name());
+ return true;
+}
+
+bool Stream::
+print(format_t *format, va_list ap)
+{
+ long lval;
+ double dval;
+ char* sval;
+ switch (format->type)
+ {
+ case DBF_ENUM:
+ case DBF_LONG:
+ lval = va_arg(ap, long);
+ return printValue(*format->priv, lval);
+ case DBF_DOUBLE:
+ dval = va_arg(ap, double);
+ return printValue(*format->priv, dval);
+ case DBF_STRING:
+ sval = va_arg(ap, char*);
+ return printValue(*format->priv, sval);
+ }
+ error("INTERNAL ERROR (%s): Illegal format type\n", name());
+ return false;
+}
+
+bool Stream::
+scan(format_t *format, void* value, size_t maxStringSize)
+{
+ // called by streamScanfN
+ long* lptr;
+ double* dptr;
+ char* sptr;
+
+ // first remove old value from inputLine (if we are scanning arrays)
+ consumedInput += currentValueLength;
+ currentValueLength = 0;
+ switch (format->type)
+ {
+ case DBF_LONG:
+ case DBF_ENUM:
+ lptr = (long*)value;
+ currentValueLength = scanValue(*format->priv, *lptr);
+ break;
+ case DBF_DOUBLE:
+ dptr = (double*)value;
+ currentValueLength = scanValue(*format->priv, *dptr);
+ break;
+ case DBF_STRING:
+ sptr = (char*)value;
+ currentValueLength = scanValue(*format->priv, sptr, maxStringSize);
+ break;
+ default:
+ error("INTERNAL ERROR (%s): Illegal format type\n", name());
+ return false;
+ }
+ if (currentValueLength < 0)
+ {
+ currentValueLength = 0;
+ return false;
+ }
+ // Don't remove scanned value from inputLine yet, because
+ // we might need the string in a later error message.
+ return true;
+}
+
+// epicsTimerNotify virtual method ///////////////////////////////////////
+
+#ifdef EPICS_3_14
+epicsTimerNotify::expireStatus Stream::
+expire(const epicsTime&)
+{
+ timerCallback();
+ return noRestart;
+}
+#else
+void Stream::
+expire(CALLBACK *pcallback)
+{
+ Stream* pstream = static_cast(pcallback->user);
+ pstream->timerCallback();
+}
+#endif
+
+// StreamCore virtual methods ////////////////////////////////////////////
+
+void Stream::
+protocolFinishHook(ProtocolResult result)
+{
+ switch (result)
+ {
+ case Success:
+ status = NO_ALARM;
+ if (flags & ValueReceived)
+ {
+ record->udf = false;
+ if (flags & InitRun)
+ {
+ // records start with UDF/INVALID,
+ // but now this record has a value
+ record->sevr = NO_ALARM;
+ record->stat = NO_ALARM;
+ }
+ }
+ break;
+ case LockTimeout:
+ case ReplyTimeout:
+ status = TIMEOUT_ALARM;
+ break;
+ case WriteTimeout:
+ status = WRITE_ALARM;
+ break;
+ case ReadTimeout:
+ status = READ_ALARM;
+ break;
+ case ScanError:
+ case FormatError:
+ status = CALC_ALARM;
+ break;
+ case Abort:
+ case Fault:
+ status = UDF_ALARM;
+ if (record->pact || record->scan == SCAN_IO_EVENT)
+ error("%s: Protocol aborted\n", name());
+ break;
+ default:
+ status = UDF_ALARM;
+ error("INTERNAL ERROR (%s): Illegal protocol result\n",
+ name());
+ break;
+
+ }
+ if (flags & InitRun)
+ {
+#ifdef EPICS_3_14
+ initDone.signal();
+#else
+ semGive(initDone);
+#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)
+ {
+ // restart protocol for next turn
+ debug("Stream::process(%s) restart async protocol\n",
+ name());
+ if (!startProtocol(StartAsync))
+ {
+ error("%s: Can't restart \"I/O Intr\" protocol\n",
+ name());
+ }
+ }
+}
+
+void Stream::
+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
+ callbackSetPriority(priority(), &timeoutCallback);
+ wdStart(timer, (timeout+1)*sysClkRateGet()/1000-1,
+ reinterpret_cast(callbackRequest),
+ reinterpret_cast(&timeoutCallback));
+#endif
+}
+
+bool Stream::
+getFieldAddress(const char* fieldname, StreamBuffer& address)
+{
+ DBADDR dbaddr;
+ if (strchr(fieldname, '.') != NULL)
+ {
+ // record.FIELD (access to other record)
+ if (dbNameToAddr(fieldname, &dbaddr) != OK) return false;
+ }
+ else
+ {
+ // FIELD in this record
+ char fullname[PVNAME_SZ + 1];
+ sprintf(fullname, "%s.%s", name(), fieldname);
+ if (dbNameToAddr(fullname, &dbaddr) != OK) return false;
+ }
+ address.append(&dbaddr, sizeof(dbaddr));
+ return true;
+}
+
+static const unsigned char dbfMapping[] =
+ {0, DBF_LONG, DBF_ENUM, DBF_DOUBLE, DBF_STRING};
+static const short typeSize[] =
+ {0, sizeof(epicsInt32), sizeof(epicsUInt16),
+ sizeof(epicsFloat64), MAX_STRING_SIZE};
+
+bool Stream::
+formatValue(const StreamFormat& format, const void* fieldaddress)
+{
+ debug("Stream::formatValue(%s, format=%%%c, fieldaddr=%p\n",
+ name(), format.conv, fieldaddress);
+
+// -- TO DO: If SCAN is "I/O Intr" and record has not been processed, --
+// -- do it now to get the latest value (only for output records?) --
+
+ if (fieldaddress)
+ {
+ // Format like "%([record.]field)..." has requested to get value
+ // from field of this or other record.
+ DBADDR* pdbaddr = (DBADDR*)fieldaddress;
+ long i;
+ long nelem = pdbaddr->no_elements;
+ size_t size = nelem * typeSize[format.type];
+ char* buffer = fieldBuffer.clear().reserve(size);
+ if (dbGet(pdbaddr, dbfMapping[format.type], buffer,
+ NULL, &nelem, NULL) != 0)
+ {
+ error("%s: dbGet(%s.%s, %s) failed\n",
+ name(),
+ pdbaddr->precord->name,
+ ((dbFldDes*)pdbaddr->pfldDes)->name,
+ pamapdbfType[dbfMapping[format.type]].strvalue);
+ return false;
+ }
+ for (i = 0; i < nelem; i++)
+ {
+ switch (format.type)
+ {
+ case enum_format:
+ if (!printValue(format,
+ (long)((epicsUInt16*)buffer)[i]))
+ return false;
+ break;
+ case long_format:
+ if (!printValue(format,
+ (long)((epicsInt32*)buffer)[i]))
+ return false;
+ break;
+ case double_format:
+ if (!printValue(format,
+ (double)((epicsFloat64*)buffer)[i]))
+ return false;
+ break;
+ case string_format:
+ if (!printValue(format, buffer+MAX_STRING_SIZE*i))
+ return false;
+ break;
+ case pseudo_format:
+ error("%s: %%(FIELD) syntax not allowed with pseudo formats\n",
+ name());
+ default:
+ error("INTERNAL ERROR %s: Illegal format.type=%d\n",
+ name(), format.type);
+ return false;
+ }
+ }
+ return true;
+ }
+ format_s fmt;
+ fmt.type = dbfMapping[format.type];
+ fmt.priv = &format;
+ debug("Stream::formatValue(%s) format=%%%c type=%s\n",
+ name(), format.conv, pamapdbfType[fmt.type].strvalue);
+ if (!writeData)
+ {
+ error("%s: No writeData() function provided\n", name());
+ return false;
+ }
+ if (writeData(record, &fmt) == ERROR)
+ {
+ debug("Stream::formatValue(%s): writeData failed\n", name());
+ return false;
+ }
+ return true;
+}
+
+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
+ // to field of this or other record.
+ DBADDR* pdbaddr = (DBADDR*)fieldaddress;
+ long nord;
+ long nelem = pdbaddr->no_elements;
+ size_t size = nelem * typeSize[format.type];
+ buffer = fieldBuffer.clear().reserve(size);
+ for (nord = 0; nord < nelem; nord++)
+ {
+ switch (format.type)
+ {
+ case long_format:
+ {
+ consumed = scanValue(format, lval);
+ if (consumed < 0) goto noMoreElements;
+ ((epicsInt32*)buffer)[nord] = lval;
+ break;
+ }
+ case enum_format:
+ {
+ consumed = scanValue(format, lval);
+ if (consumed < 0) goto noMoreElements;
+ ((epicsUInt16*)buffer)[nord] = (epicsUInt16)lval;
+ break;
+ }
+ case double_format:
+ {
+ consumed = scanValue(format, dval);
+ if (consumed < 0) goto noMoreElements;
+ ((epicsFloat64*)buffer)[nord] = dval;
+ break;
+ }
+ case string_format:
+ {
+ consumed = scanValue(format,
+ buffer+MAX_STRING_SIZE*nord, MAX_STRING_SIZE);
+ if (consumed < 0) goto noMoreElements;
+ break;
+ }
+ default:
+ error("INTERNAL ERROR: Stream::matchValue %s: "
+ "Illegal format type\n", name());
+ return false;
+ }
+ consumedInput += consumed;
+ }
+noMoreElements:
+ if (!nord)
+ {
+ // scan error: set other record to alarm status
+ if (pdbaddr->precord != record)
+ {
+ recGblSetSevr(pdbaddr->precord, CALC_ALARM, INVALID_ALARM);
+ if (!INIT_RUN)
+ {
+ // process other record to send alarm monitor
+ dbProcess(pdbaddr->precord);
+ }
+ }
+ return false;
+ }
+ 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",
+ name(),
+ pdbaddr->precord->name,
+ ((dbFldDes*)pdbaddr->pfldDes)->name);
+ putfunc = "dbPut";
+ status = dbPut(pdbaddr, dbfMapping[format.type], fieldBuffer(), nord);
+ if (INIT_RUN && pdbaddr->precord != record)
+ {
+ // clean error status of other record in @init
+ pdbaddr->precord->udf = false;
+ pdbaddr->precord->sevr = NO_ALARM;
+ pdbaddr->precord->stat = NO_ALARM;
+ }
+ }
+ else
+ {
+ // write into other record, thus process it
+ debug("Stream::matchValue(%s): dbPutField(%s.%s,...)\n",
+ name(),
+ pdbaddr->precord->name,
+ ((dbFldDes*)pdbaddr->pfldDes)->name);
+ putfunc = "dbPutField";
+ status = dbPutField(pdbaddr, dbfMapping[format.type], fieldBuffer(), nord);
+ }
+ if (status != 0)
+ {
+ flags &= ~ScanTried;
+ switch (format.type)
+ {
+ case long_format:
+ case enum_format:
+ error("%s: %s(%s.%s, %s, %li) failed\n",
+ putfunc, name(), pdbaddr->precord->name,
+ ((dbFldDes*)pdbaddr->pfldDes)->name,
+ pamapdbfType[dbfMapping[format.type]].strvalue,
+ lval);
+ return false;
+ case double_format:
+ error("%s: %s(%s.%s, %s, %#g) failed\n",
+ putfunc, name(), pdbaddr->precord->name,
+ ((dbFldDes*)pdbaddr->pfldDes)->name,
+ pamapdbfType[dbfMapping[format.type]].strvalue,
+ dval);
+ return false;
+ case string_format:
+ error("%s: %s(%s.%s, %s, \"%s\") failed\n",
+ putfunc, name(), pdbaddr->precord->name,
+ ((dbFldDes*)pdbaddr->pfldDes)->name,
+ pamapdbfType[dbfMapping[format.type]].strvalue,
+ buffer);
+ return false;
+ default:
+ return false;
+ }
+ }
+ return true;
+ }
+ // no fieldaddress (the "normal" case)
+ format_s fmt;
+ fmt.type = dbfMapping[format.type];
+ fmt.priv = &format;
+ if (!readData)
+ {
+ error("%s: No readData() function provided\n", name());
+ return false;
+ }
+ currentValueLength = 0;
+ convert = readData(record, &fmt); // this will call scan()
+ if (convert == ERROR)
+ {
+ debug("Stream::matchValue(%s): readData failed\n", name());
+ if (currentValueLength > 0)
+ {
+ error("%s: Record does not accept input \"%s%s\"\n",
+ name(), inputLine.expand(consumedInput, 19)(),
+ inputLine.length()-consumedInput > 20 ? "..." : "");
+ flags &= ~ScanTried;
+ }
+ return false;
+ }
+ flags |= ValueReceived;
+ consumedInput += currentValueLength;
+ return true;
+}
+
+#ifdef EPICS_3_14
+
+void streamExecuteCommand(CALLBACK *pcallback)
+{
+ Stream* pstream = static_cast(pcallback->user);
+
+ if (iocshCmd(pstream->outputLine()) != OK)
+ {
+ pstream->execCallback(StreamIoFault);
+ }
+ else
+ {
+ pstream->execCallback(StreamIoSuccess);
+ }
+}
+#else
+extern "C" int execute (const char *cmd);
+
+void streamExecuteCommand(CALLBACK *pcallback)
+{
+ Stream* pstream = static_cast(pcallback->user);
+
+ if (execute(pstream->outputLine()) != OK)
+ {
+ pstream->execCallback(StreamBusInterface::ioFault);
+ }
+ else
+ {
+ pstream->execCallback(StreamBusInterface::ioSuccess);
+ }
+}
+#endif
+
+bool Stream::
+execute()
+{
+ callbackSetPriority(priority(), &commandCallback);
+ callbackRequest(&commandCallback);
+ return true;
+}
+
+void Stream::
+lockMutex()
+{
+#ifdef EPICS_3_14
+ mutex.lock();
+#else
+ semTake(mutex, WAIT_FOREVER);
+#endif
+}
+
+void Stream::
+releaseMutex()
+{
+#ifdef EPICS_3_14
+ mutex.unlock();
+#else
+ semGive(mutex);
+#endif
+}
diff --git a/src/StreamError.cc b/src/StreamError.cc
new file mode 100644
index 0000000..d325854
--- /dev/null
+++ b/src/StreamError.cc
@@ -0,0 +1,103 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is error and debug message handling 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 "StreamError.h"
+#include
+#include
+
+int streamDebug = 0;
+extern "C" {
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+FILE *StreamDebugFile = NULL;
+}
+
+#ifndef va_copy
+#ifdef __va_copy
+#define va_copy __va_copy
+#endif
+#endif
+
+/* You can globally change the printTimestamp function
+ by setting the StreamPrintTimestampFunction variable
+ to your own function.
+*/
+static void printTimestamp(char* buffer, int size)
+{
+ time_t t;
+ struct tm tm;
+ time(&t);
+#ifdef _WIN32
+ tm = *localtime(&t);
+#else
+ localtime_r(&t, &tm);
+#endif
+ strftime(buffer, size, "%Y/%m/%d %H:%M:%S", &tm);
+}
+
+void (*StreamPrintTimestampFunction)(char* buffer, int size) = printTimestamp;
+
+void StreamError(const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ StreamVError(fmt, args);
+ va_end(args);
+}
+
+void StreamVError(const char* fmt, va_list args)
+{
+ char timestamp[40];
+ StreamPrintTimestampFunction(timestamp, 40);
+#ifdef va_copy
+ if (StreamDebugFile)
+ {
+ va_list args2;
+ va_copy(args2, args);
+ fprintf(StreamDebugFile, "%s ", timestamp);
+ vfprintf(StreamDebugFile, fmt, args2);
+ fflush(StreamDebugFile);
+ va_end(args2);
+ }
+#endif
+ fprintf(stderr, "\033[31;1m");
+ fprintf(stderr, "%s ", timestamp);
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\033[0m");
+}
+
+int StreamDebugClass::
+print(const char* fmt, ...)
+{
+ va_list args;
+ char timestamp[40];
+ StreamPrintTimestampFunction(timestamp, 40);
+ va_start(args, fmt);
+ const char* f = strrchr(file, '/');
+ if (f) f++; else f = file;
+ FILE* fp = StreamDebugFile ? StreamDebugFile : stderr;
+ fprintf(fp, "%s ", timestamp);
+ fprintf(fp, "%s:%d: ", f, line);
+ vfprintf(fp, fmt, args);
+ fflush(fp);
+ va_end(args);
+ return 1;
+}
+
diff --git a/src/StreamError.h b/src/StreamError.h
new file mode 100644
index 0000000..d36ad53
--- /dev/null
+++ b/src/StreamError.h
@@ -0,0 +1,62 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is error and debug message handling 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. *
+* *
+***************************************************************/
+
+#ifndef StreamError_h
+#define StreamError_h
+
+#include
+#include
+
+#ifndef __GNUC__
+#define __attribute__(x)
+#endif
+
+extern int streamDebug;
+extern void (*StreamPrintTimestampFunction)(char* buffer, int size);
+
+void StreamError(const char* fmt, ...)
+__attribute__ ((format(printf,1,2)));
+
+void StreamVError(const char* fmt, va_list args)
+__attribute__ ((format(printf,1,0)));
+
+class StreamDebugClass
+{
+ const char* file;
+ int line;
+public:
+ StreamDebugClass(const char* file, int line) :
+ file(file), line(line) {}
+ int print(const char* fmt, ...)
+ __attribute__ ((format(printf,2,3)));
+};
+
+inline StreamDebugClass
+StreamDebugObject(const char* file, int line)
+{ return StreamDebugClass(file, line); }
+
+#define error StreamError
+#define debug (!streamDebug)?0:StreamDebugObject(__FILE__,__LINE__).print
+
+#if (__GNUC__ == 2 && __GNUC_MINOR__ == 7)
+/* Bug in cygnus-2.7.2 compiler: temporary objects crash the compiler */
+#define NO_TEMPORARY
+#endif
+
+#endif
diff --git a/src/StreamFormat.h b/src/StreamFormat.h
new file mode 100644
index 0000000..7e049e8
--- /dev/null
+++ b/src/StreamFormat.h
@@ -0,0 +1,55 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This header defines the format stucture used to interface *
+* format converters and record interfaces to 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. *
+* *
+***************************************************************/
+
+#ifndef StreamFormat_h
+#define StreamFormat_h
+
+typedef enum {
+ left_flag = 0x01,
+ sign_flag = 0x02,
+ space_flag = 0x04,
+ alt_flag = 0x08,
+ zero_flag = 0x10,
+ skip_flag = 0x20
+} StreamFormatFlag;
+
+typedef enum {
+ long_format = 1,
+ enum_format = 2,
+ double_format = 3,
+ string_format = 4,
+ pseudo_format = 5
+} StreamFormatType;
+
+extern const char* StreamFormatTypeStr[];
+
+typedef struct StreamFormat
+{
+ char conv;
+ StreamFormatType type;
+ unsigned char flags;
+ short prec;
+ unsigned short width;
+ unsigned short infolen;
+ const char* info;
+} StreamFormat;
+
+#endif
diff --git a/src/StreamFormatConverter.cc b/src/StreamFormatConverter.cc
new file mode 100644
index 0000000..e4f9d80
--- /dev/null
+++ b/src/StreamFormatConverter.cc
@@ -0,0 +1,435 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is the format converter base and includes the standard *
+* format converters 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 "StreamFormatConverter.h"
+#include "StreamFormat.h"
+#include "StreamError.h"
+
+StreamFormatConverter* StreamFormatConverter::
+registered [256];
+
+StreamFormatConverter::
+~StreamFormatConverter()
+{
+}
+
+void StreamFormatConverter::
+provides(const char* name, const char* provided)
+{
+ _name = name;
+ const unsigned char* p;
+ for (p = reinterpret_cast(provided);
+ *p; p++)
+ {
+ registered[*p] = this;
+ }
+}
+
+bool StreamFormatConverter::
+printLong(const StreamFormat& fmt, StreamBuffer&, long)
+{
+ error("Unimplemented printLong method\n for %%%c format",
+ fmt.conv);
+ return false;
+}
+
+bool StreamFormatConverter::
+printDouble(const StreamFormat& fmt, StreamBuffer&, double)
+{
+ error("Unimplemented printDouble method for %%%c format\n",
+ fmt.conv);
+ return false;
+}
+
+bool StreamFormatConverter::
+printString(const StreamFormat& fmt, StreamBuffer&, const char*)
+{
+ error("Unimplemented printString method for %%%c format\n",
+ fmt.conv);
+ return false;
+}
+
+bool StreamFormatConverter::
+printPseudo(const StreamFormat& fmt, StreamBuffer&)
+{
+ error("Unimplemented printPseudo method for %%%c format\n",
+ fmt.conv);
+ return false;
+}
+
+int StreamFormatConverter::
+scanLong(const StreamFormat& fmt, const char*, long&)
+{
+ error("Unimplemented scanLong method for %%%c format\n",
+ fmt.conv);
+ return -1;
+}
+
+int StreamFormatConverter::
+scanDouble(const StreamFormat& fmt, const char*, double&)
+{
+ error("Unimplemented scanDouble method for %%%c format\n",
+ fmt.conv);
+ return -1;
+}
+
+int StreamFormatConverter::
+scanString(const StreamFormat& fmt, const char*, char*, size_t)
+{
+ error("Unimplemented scanString method for %%%c format\n",
+ fmt.conv);
+ return -1;
+}
+
+int StreamFormatConverter::
+scanPseudo(const StreamFormat& fmt, StreamBuffer&, long&)
+{
+ error("Unimplemented scanPseudo method for %%%c format\n",
+ fmt.conv);
+ return -1;
+}
+
+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);
+}
+
+// Standard Long Converter for 'diouxX'
+
+class StdLongConverter : public StreamFormatConverter
+{
+ int parse(const StreamFormat& fmt, StreamBuffer& output, const char*& value, bool scanFormat);
+ bool printLong(const StreamFormat& fmt, StreamBuffer& output, long value);
+ int scanLong(const StreamFormat& fmt, const char* input, long& value);
+};
+
+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");
+ return long_format;
+}
+
+bool StdLongConverter::
+printLong(const StreamFormat& fmt, StreamBuffer& output, long value)
+{
+ output.printf(fmt.info, value);
+ return true;
+}
+
+int StdLongConverter::
+scanLong(const StreamFormat& fmt, const char* input, long& 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;
+ }
+ return length;
+}
+
+RegisterConverter (StdLongConverter, "diouxX");
+
+// Standard Double Converter for 'feEgG'
+
+class StdDoubleConverter : public StreamFormatConverter
+{
+ virtual int parse(const StreamFormat&, StreamBuffer&, const char*&, bool);
+ virtual bool printDouble(const StreamFormat&, StreamBuffer&, double);
+ virtual int scanDouble(const StreamFormat&, const char*, double&);
+};
+
+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");
+ return double_format;
+}
+
+bool StdDoubleConverter::
+printDouble(const StreamFormat& fmt, StreamBuffer& output, double value)
+{
+ output.printf(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;
+ }
+ return length;
+}
+
+RegisterConverter (StdDoubleConverter, "feEgG");
+
+// Standard String Converter for 's'
+
+class StdStringConverter : public StreamFormatConverter
+{
+ virtual int parse(const StreamFormat&, StreamBuffer&, const char*&, bool);
+ virtual bool printString(const StreamFormat&, StreamBuffer&, const char*);
+ virtual int scanString(const StreamFormat&, const char*, char*, size_t);
+};
+
+int StdStringConverter::
+parse(const StreamFormat& fmt, StreamBuffer& info,
+ const char*& source, bool scanFormat)
+{
+ if (fmt.flags & (sign_flag|space_flag|zero_flag|alt_flag))
+ {
+ error("Use of modifiers '+', ' ', '0', '#' "
+ "not allowed with %%%c 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(fmt.conv);
+ if (scanFormat) info.append("%n");
+ return string_format;
+}
+
+bool StdStringConverter::
+printString(const StreamFormat& fmt, StreamBuffer& output, const char* value)
+{
+ output.printf(fmt.info, value);
+ return true;
+}
+
+int StdStringConverter::
+scanString(const StreamFormat& fmt, const char* input,
+ char* value, size_t maxlen)
+{
+ int length = -1;
+ if (*input == '\0')
+ {
+ // match empty string
+ value[0] = '\0';
+ return 0;
+ }
+ if (fmt.flags & skip_flag)
+ {
+ if (sscanf(input, fmt.info, &length) < 0) return -1;
+ }
+ else
+ {
+ char tmpformat[10];
+ const char* f;
+ if (maxlen <= fmt.width || fmt.width == 0)
+ {
+ // assure not to read too much
+ sprintf(tmpformat, "%%%d%c%%n", maxlen-1, fmt.conv);
+ f = tmpformat;
+ }
+ 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
+ }
+ return length;
+}
+
+RegisterConverter (StdStringConverter, "s");
+
+// Standard Characters Converter for 'c'
+
+class StdCharsConverter : public StdStringConverter
+{
+ virtual int parse(const StreamFormat&, StreamBuffer&, const char*&, bool);
+ virtual bool printLong(const StreamFormat&, StreamBuffer&, long);
+ // scanString is inherited from %s format
+};
+
+int StdCharsConverter::
+parse(const StreamFormat& fmt, StreamBuffer& info,
+ const char*& source, bool scanFormat)
+{
+ if (fmt.flags & (sign_flag|space_flag|zero_flag|alt_flag))
+ {
+ error("Use of modifiers '+', ' ', '0', '#' "
+ "not allowed with %%%c 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(fmt.conv);
+ if (scanFormat)
+ {
+ info.append("%n");
+ return string_format;
+ }
+ return long_format;
+}
+
+bool StdCharsConverter::
+printLong(const StreamFormat& fmt, StreamBuffer& output, long value)
+{
+ output.printf(fmt.info, value);
+ return true;
+}
+
+RegisterConverter (StdCharsConverter, "c");
+
+// Standard Charset Converter for '['
+
+class StdCharsetConverter : public StreamFormatConverter
+{
+ virtual int parse(const StreamFormat&, StreamBuffer&, const char*&, bool);
+ virtual int scanString(const StreamFormat&, const char*, char*, size_t);
+ // no print method, %[ is readonly
+};
+
+int StdCharsetConverter::
+parse(const StreamFormat& fmt, StreamBuffer& info,
+ const char*& source, bool scanFormat)
+{
+ if (!scanFormat)
+ {
+ error("Format conversion %%[ is only allowed in input formats\n");
+ return false;
+ }
+ 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);
+ 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;
+ }
+ info.printf("%%%d[", fmt.width);
+ while (*source && *source != ']')
+ {
+ if (*source == esc) source++;
+ info.append(*source++);
+ }
+ if (!*source) {
+ error("Missing ']' after %%[ format conversion\n");
+ return false;
+ }
+ source++; // consume ']'
+ info.append("]%n");
+ return string_format;
+}
+
+int StdCharsetConverter::
+scanString(const StreamFormat& fmt, const char* input,
+ char* value, size_t maxlen)
+{
+ int length = -1;
+ if (fmt.flags & skip_flag)
+ {
+ if (sscanf (input, fmt.info, &length) < 0) return -1;
+ }
+ else
+ {
+ 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);
+ }
+ return length;
+}
+
+RegisterConverter (StdCharsetConverter, "[");
diff --git a/src/StreamFormatConverter.h b/src/StreamFormatConverter.h
new file mode 100644
index 0000000..3bc03bb
--- /dev/null
+++ b/src/StreamFormatConverter.h
@@ -0,0 +1,162 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is the format converter header 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. *
+* *
+***************************************************************/
+
+#ifndef StreamFormatConverter_h
+#define StreamFormatConverter_h
+
+#include "StreamFormat.h"
+#include "StreamBuffer.h"
+
+#define esc (0x1b)
+
+template
+class StreamFormatConverterRegistrar
+{
+public:
+ StreamFormatConverterRegistrar(const char* name, const char* provided) {
+ static C prototype;
+ prototype.provides(name, provided);
+ }
+};
+
+class StreamFormatConverter
+{
+ static StreamFormatConverter* registered [];
+ const char* _name;
+public:
+ virtual ~StreamFormatConverter();
+ const char* name() { return _name; }
+ void provides(const char* name, const char* provided);
+ static StreamFormatConverter* find(unsigned char c);
+ virtual int parse(const StreamFormat& fmt,
+ StreamBuffer& info, const char*& source, bool scanFormat) = 0;
+ virtual bool printLong(const StreamFormat& fmt,
+ StreamBuffer& output, long value);
+ virtual bool printDouble(const StreamFormat& fmt,
+ StreamBuffer& output, double value);
+ virtual bool printString(const StreamFormat& fmt,
+ StreamBuffer& output, const char* value);
+ virtual bool printPseudo(const StreamFormat& fmt,
+ StreamBuffer& output);
+ virtual int scanLong(const StreamFormat& fmt,
+ const char* input, long& value);
+ virtual int scanDouble(const StreamFormat& fmt,
+ const char* input, double& value);
+ virtual int scanString(const StreamFormat& fmt,
+ const char* input, char* value, size_t maxlen);
+ virtual int scanPseudo(const StreamFormat& fmt,
+ StreamBuffer& inputLine, long& cursor);
+};
+
+inline StreamFormatConverter* StreamFormatConverter::
+find(unsigned char c) {
+ return registered[c];
+}
+
+#define RegisterConverter(converter, conversions) \
+template class StreamFormatConverterRegistrar; \
+StreamFormatConverterRegistrar \
+registrar_##converter(#converter,conversions); \
+void* ref_##converter = ®istrar_##converter\
+
+/****************************************************************************
+* A user defined converter class inherits public from StreamFormatConverter
+* and handles one or more conversion characters.
+* Print and scan of the same conversion character must be handled by the
+* same class but not both need to be supported.
+*
+* parse()
+* =======
+* This function is called when the protocol is parsed (at initialisation)
+* whenever one of the conversions handled by your converter is found.
+* The fields fmt.conv, fmt.flags, fmt.prec, and fmt.width have
+* already been filled in. If a scan format is parsed, scanFormat is true. If
+* a print format is parsed, scanFormat is false.
+*
+* The source 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 source pointer
+* so that is points to the first character after the format string.
+*
+* Example:
+*
+* source source
+* before after
+* V V
+* "%39[1-9]constant text"
+* |
+* conversion
+* character
+*
+* You can write any string to info you need in print*() or scan*(). This will
+* probably be necessary if you have taken characters from the format string.
+*
+* Return long_format, double_format, string_format, or enum_format
+* depending on the datatype associated with the conversion character.
+* You need not 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.
+*
+* 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 pseudo_format.
+*
+* Return false if there is any parse error or if print or scan is requested
+* but not supported by this conversion.
+*
+* print[Long|Double|String|Pseudo](), scan[Long|Double|String|Pseudo]()
+* =================
+* Provide a print*() and/or scan*() method appropriate for the data type
+* you have returned in the parse() method. That method is called whenever
+* the conversion appears in an output or input, resp.
+* You only need to implement the flavour of print and/or scan suitable for
+* the datatype returned by parse().
+*
+* Now, fmt.type contains the value returned by parse(). With fmt.info()
+* you will get the string you have written to info in parse() (null terminated).
+* The length of the info string can be found in fmt.infolen.
+*
+* In print*(), append the converted value to output. Do not modify what is
+* already in output (unless you really know what you're doing).
+* Return true on success, false on failure.
+*
+* In scan*(), read the value from input and return the number of consumed
+* bytes. In the string version, don't write more bytes than maxlen! If the
+* skip_flag is set, you don't need to write to value, since the value will be
+* discarded anyway. Return -1 on failure.
+*
+*
+* Register your class
+* ===================
+* To register your class, write
+*
+* RegisterConverter (your_class, "conversions");
+*
+* in the global context of your file.
+* "conversions" is a string containing all conversion characters
+* handled by your class.
+*
+* For examples see StreamFormatConverter.cc.
+*
+* HINT: Do not branch depending on the conversion character.
+* Provide multiple classes, that's more efficient.
+*
+****************************************************************************/
+
+#endif
diff --git a/src/StreamProtocol.cc b/src/StreamProtocol.cc
new file mode 100644
index 0000000..3781b6e
--- /dev/null
+++ b/src/StreamProtocol.cc
@@ -0,0 +1,1661 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is the protocol parser 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
+#include
+#include
+#include "StreamProtocol.h"
+#include "StreamFormatConverter.h"
+#include "StreamError.h"
+
+const char* StreamFormatTypeStr[] = {
+ "none", "long", "enum", "double", "string", "pseudo"
+};
+
+class StreamProtocolParser::Protocol::Variable
+{
+ friend class Protocol;
+ friend class StreamProtocolParser;
+
+ Variable* next;
+ const StreamBuffer name;
+ StreamBuffer value;
+ int line;
+ bool used;
+
+ Variable(const char* name, int line, size_t startsize=0);
+ Variable(const Variable& v);
+ ~Variable();
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// StreamProtocolParser
+
+StreamProtocolParser* StreamProtocolParser::parsers = NULL;
+const char* StreamProtocolParser::path = ".";
+static const char* specialChars = " ,;{}=()$'\"+-*/";
+
+// Client destructor
+StreamProtocolParser::Client::
+~Client()
+{
+}
+
+// Private constructor
+StreamProtocolParser::
+StreamProtocolParser(FILE* file, const char* filename)
+ : filename(filename), file(file), globalSettings(filename)
+{
+
+ next = parsers;
+ parsers = this;
+ // start parsing in global context
+ protocols = NULL;
+ line = 1;
+ quote = false;
+ valid = parseProtocol(globalSettings, globalSettings.commands);
+}
+
+// Private destructor
+StreamProtocolParser::
+~StreamProtocolParser()
+{
+ delete protocols;
+ 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()
+{
+ Protocol* p;
+ printf("Report of protocol file '%s'\n", filename());
+ printf(" GLOBAL:\n");
+ globalSettings.report();
+ printf(" PROTOCOLS:\n");
+ for (p = protocols; p; p = p->next)
+ {
+ p->report();
+ }
+}
+
+// API function: read protocol from file, create parser if necessary
+// RETURNS: a copy of a protocol that must be deleted by the caller
+// SIDEEFFECTS: file IO, memory allocation for parsers
+StreamProtocolParser::Protocol* StreamProtocolParser::
+getProtocol(const char* filename, const StreamBuffer& protocolAndParams)
+{
+ StreamProtocolParser* parser;
+
+ // Have we already seen this file?
+ for (parser = parsers; parser; parser = parser->next)
+ {
+ if (parser->filename.equals(filename))
+ {
+ if (!parser->valid)
+ {
+ error("Protocol file '%s' is invalid (see above)\n",
+ filename);
+ return NULL;
+ }
+ break;
+ }
+ }
+ if (!parser)
+ {
+ // If not, read it.
+ parser = readFile(filename);
+ if (!parser)
+ {
+ // We could not read the file.
+ return NULL;
+ }
+ }
+ return parser->getProtocol(protocolAndParams);
+}
+
+// API function: free all parser resources allocated by any getProtocol()
+// Call this function after the last getProtocol() to clean up.
+void StreamProtocolParser::
+free()
+{
+ delete parsers;
+ parsers = NULL;
+}
+
+/*
+STEP 1: Parse the protocol file
+
+Read the complete file, check structure and break it into protocols,
+handlers and assignments.
+Substitute protocol references and variable references.
+No replacement of positional parameters ($1...$9).
+No coding of parameters, commands, functions, or formats yet. We'll do
+this after protocol arguments have been replaced.
+*/
+
+StreamProtocolParser* StreamProtocolParser::
+readFile(const char* filename)
+{
+ FILE* file;
+#ifdef windows
+ const char pathseparator = ';';
+ const char dirseparator = '\\';
+#else
+ const char pathseparator = ':';
+ const char dirseparator = '/';
+#endif
+ StreamProtocolParser* parser;
+ const char *p;
+ const char *s;
+ size_t n;
+ StreamBuffer dir;
+
+ // look for filename in every dir in search path
+ for (p = path; *p; p += n)
+ {
+ dir.clear();
+ // get next dir from search path (avoiding strtok, strsep, strcspn)
+ s = strchr(p, pathseparator);
+ if (s) n = s - p;
+ else n = strlen(p);
+ dir.append(p, n);
+ if (n && p[n-1] != dirseparator) dir.append(dirseparator);
+ if (s) p++;
+ // append filename
+ dir.append(filename);
+ // try to read the file
+ debug("StreamProtocolParser::readFile: try '%s'\n", dir());
+ file = fopen(dir(), "r");
+ if (file)
+ {
+ // file found; create a parser to read it
+ parser = new StreamProtocolParser(file, filename);
+ fclose(file);
+ if (!parser->valid) return NULL;
+// printf(
+// "/---------------------------------------------------------------------\\\n");
+// parser->report();
+// printf(
+// "\\---------------------------------------------------------------------/\n");
+ return parser;
+ }
+ }
+ error("Can't find readable file '%s' in '%s'\n", filename, path);
+ return NULL;
+}
+
+/*
+STEP 2: Compile protocols to executable format
+
+Make copy of one of the parsed protocols.
+Replace positional parameters.
+Ask client how to compile formats and commands.
+*/
+
+// Get a copy of a protocol
+// Substitutions in protocolname replace positional parameters
+StreamProtocolParser::Protocol* StreamProtocolParser::
+getProtocol(const StreamBuffer& protocolAndParams)
+{
+ StreamBuffer name = protocolAndParams;
+ // make name case insensitive
+ char* p;
+ for (p = name(); *p; p++) *p = tolower(*p);
+ // find and make a copy with parameters inserted
+ Protocol* protocol;
+ for (protocol = protocols; protocol; protocol = protocol->next)
+ {
+ if (protocol->protocolname.equals(name()))
+ // constructor also replaces parameters
+ return new Protocol(*protocol, name, 0);
+ }
+ error("Protocol '%s' not found in protocol file '%s'\n",
+ name(), filename());
+ return NULL;
+}
+
+inline bool StreamProtocolParser::
+isGlobalContext(const StreamBuffer* commands)
+{
+ return commands == globalSettings.commands;
+}
+
+inline bool StreamProtocolParser::
+isHandlerContext(Protocol& protocol, const StreamBuffer* commands)
+{
+ return commands != protocol.commands;
+}
+
+bool StreamProtocolParser::
+parseProtocol(Protocol& protocol, StreamBuffer* commands)
+{
+ StreamBuffer token;
+ int op;
+ int startline;
+
+ commands->clear();
+ while (true) // assignment and protocol parsing loop
+ {
+ // expect command name, protoclol name,
+ // handler name, or variable name
+ token.clear();
+ startline = line;
+ if (!readToken(token, " ,;{}=()$'\"", isGlobalContext(commands)))
+ return false;
+ debug("StreamProtocolParser::parseProtocol:"
+ " token='%s'\n", token.expand()());
+
+ if (token[0] == 0)
+ {
+ // terminate at EOF in global context
+ return true;
+ }
+ if (strchr(" ;", token[0]))
+ {
+ // skip whitespaces, comments and ';'
+ // this must come after (token[0] == 0) check
+ continue;
+ }
+ if (token[0] == '}')
+ {
+ if (!isGlobalContext(commands))
+ {
+ // end of protocol or handler definition
+ return true;
+ }
+ errorMsg("Stray '}' in global context\n");
+ return false;
+ }
+ if (strchr("{=", token[0]))
+ {
+ errorMsg("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());
+ return false;
+ }
+ if (op == '=')
+ {
+ // variable assignment
+ if (isHandlerContext(protocol, commands))
+ {
+ errorMsg("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",
+ token[0], token());
+ return false;
+ }
+ if (!parseAssignment(token(), protocol))
+ {
+ line = startline;
+ errorMsg("in variable assignment '%s = ...'\n", token());
+ return false;
+ }
+ continue;
+ }
+ if (op == '{') // protocol or handler definition
+ {
+ token.truncate(-1-(int)sizeof(int));
+ if (token[0] == '@') // handler
+ {
+ if (isHandlerContext(protocol, commands))
+ {
+ errorMsg("Handlers are not allowed in handlers: %s\n",
+ token());
+ return false;
+ }
+ StreamBuffer* handler = protocol.createVariable(token(), line);
+ handler->clear(); // override existing handler
+ if (!parseProtocol(protocol, handler))
+ {
+ line = startline;
+ errorMsg("in handler '%s'\n", token());
+ return false;
+ }
+ continue;
+ }
+ // protocol definition
+ if (!isGlobalContext(commands))
+ {
+ errorMsg("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()))
+ {
+ errorMsg("Protocol '%s' redefined\n", token());
+ return false;
+ }
+ }
+ Protocol* pP = new Protocol(protocol, token, startline);
+ if (!parseProtocol(*pP, pP->commands))
+ {
+ line = startline;
+ errorMsg("in protocol '%s'\n", token());
+ delete pP;
+ return false;
+ }
+ // append new protocol to parser
+ *ppP = pP;
+ continue;
+ }
+ // Must be a command or a protocol reference.
+ if (isGlobalContext(commands))
+ {
+ errorMsg("Expect '=' or '{' instead of '%c' after '%s'\n",
+ op, token());
+ return false;
+ }
+ if (op == ';' || op == '}') // no arguments
+ {
+ if (op == '}') ungetc(op, file);
+ // Check for protocol reference
+ Protocol* p;
+ for (p = protocols; p; p = p->next)
+ {
+ if (p->protocolname.equals(token()))
+ {
+ commands->append(*p->commands);
+ break;
+ }
+ }
+ if (p) continue;
+ }
+ // must be a command (validity will be checked later)
+ commands->append(token); // is null separated
+ ungetc(op, file); // put back first char of value
+ if (parseValue(*commands, true) == false)
+ {
+ line = startline;
+ errorMsg("after command '%s'\n", token());
+ return false;
+ }
+ debug("parseProtocol: command '%s'\n", (*commands).expand()());
+ commands->append('\0'); // terminate value with null byte
+ }
+}
+
+int StreamProtocolParser::
+readChar()
+{
+ int c;
+ c = getc(file);
+ if (isspace(c) || c == '#') // blanks or comments
+ {
+ do {
+ if (c == '#') // comments
+ {
+ while ((c = getc(file)) != EOF && c != '\n');
+ }
+ if (c == '\n')
+ {
+ ++line; // count newlines
+ }
+ c = getc(file);
+ } while (isspace(c) || c == '#');
+ if (c != EOF) ungetc(c, file); // put back non-blank
+ c = ' '; // return one space for all spaces and comments
+ }
+ return c;
+}
+
+bool StreamProtocolParser::
+readToken(StreamBuffer& buffer, const char* specialchars, bool eofAllowed)
+{
+/*
+a token is
+ - a string in "" or '' (possibly with escaped \" or \' inside)
+ - one of specialchars
+ - EOF
+ - a variable reference starting with $ (quoted or unquoted)
+ - a word of chars not containing any of the above mapped to lowercase
+
+All comments starting with # ending at end of line are whitespaces.
+Sequences of whitespaces are be compressed into one blank.
+EOF translates to a null byte.
+Token is copied from file to buffer, variable names, words and strings are
+terminated and the line number is stored for later use.
+Each time newline is read, line is incremented.
+*/
+ if (!specialchars) specialchars = specialChars;
+ long token = buffer.length();
+ int l = line;
+
+ int c = readChar();
+ if (c == '$')
+ {
+ // a variable
+ debug("StreamProtocolParser::readToken: Variable\n");
+ buffer.append(c);
+ if (quote) buffer.append('"'); // mark as quoted variable
+ c = getc(file);
+ if (c >= '0' && c <= '9')
+ {
+ // positional parameter $0 ... $9
+ buffer.append(c);
+ buffer.append('\0').append(&l, sizeof(l)); // append line number
+ return true;
+ }
+ if (c == '{')
+ {
+ int q = quote;
+ quote = false;
+ // variable like ${xyz}
+ if (!readToken(buffer, "{}=;")) return false;
+ debug("StreamProtocolParser::readToken: Variable '%s' in {}\n",
+ buffer(token));
+ c = getc(file);
+ if (c != '}')
+ {
+ errorMsg("Expect '}' instead of '%c' after: %s\n",
+ c, buffer(token));
+ return false;
+ }
+ quote = q;
+ return true;
+ }
+ if (c == EOF)
+ {
+ errorMsg("Unexpected end of file after '$'\n");
+ return false;
+ }
+ if (strchr (specialchars, c))
+ {
+ errorMsg("Unexpected '%c' after '$'\n,", c);
+ return false;
+ }
+ // variable like $xyz handled as word token
+ }
+ else if (quote || c == '\'' || c == '"')
+ {
+ debug("StreamProtocolParser::readToken: Quoted string\n");
+ // quoted string
+ if (!quote)
+ {
+ quote = c;
+ c = getc(file);
+ }
+ buffer.append(quote);
+ while (quote)
+ {
+ if (c == EOF || c == '\n')
+ {
+ errorMsg("Unterminated quoted string: %s\n",
+ buffer(token));
+ return false;
+ }
+ if (c == '$' && buffer[-1] == '\\')
+ {
+ // quoted variable reference
+ // terminate string here and do variable in next pass
+ buffer[-1] = quote;
+ ungetc(c, file);
+ break;
+ }
+ buffer.append(c);
+ if (c == quote && buffer[-1] != '\\')
+ {
+ quote = false;
+ break;
+ }
+ c = getc(file);
+ }
+ buffer.append('\0').append(&l, sizeof(l)); // append line number
+ return true;
+ }
+ else if (c == EOF)
+ {
+ // end of file
+ if (!eofAllowed)
+ {
+ errorMsg("Unexpected end of file\n");
+ return false;
+ }
+ buffer.append('\0');
+ return true;
+ }
+ else if (strchr (specialchars, c))
+ {
+ debug("StreamProtocolParser::readToken: Special '%c'\n", c);
+ // a single character
+ buffer.append(c);
+ return true;
+ }
+ // a word or (variable name)
+ debug("StreamProtocolParser::readToken: word\n");
+ while (1)
+ {
+ buffer.append(tolower(c));
+ if ((c = readChar()) == EOF) break;
+ if (strchr (specialchars, c))
+ {
+ ungetc(c, file); // put back char of next token
+ break;
+ }
+ }
+ debug("StreamProtocolParser::readToken: word='%s' c='%c'\n",
+ buffer(token), c);
+ buffer.append('\0').append(&l, sizeof(l)); // append line number
+ return true;
+}
+
+bool StreamProtocolParser::
+parseAssignment(const char* name, Protocol& protocol)
+{
+ StreamBuffer value;
+
+ if (!parseValue (value)) return false;
+ *protocol.createVariable(name, line) = value; // transfer value
+ return true;
+}
+
+bool StreamProtocolParser::
+parseValue(StreamBuffer& buffer, bool lazy)
+{
+ long token;
+ int c;
+
+ do c = readChar(); while (c == ' '); // skip leading spaces
+ ungetc (c, file);
+ while (true)
+ {
+ token = buffer.length(); // start of next token
+ if (!readToken(buffer)) return false;
+ debug("StreamProtocolParser::parseValue:%d: %s\n",
+ line, buffer.expand(token)());
+ c = buffer[token];
+ if (c == '$') // a variable
+ {
+ long varname = token+1;
+ if (buffer[varname] == '"') varname++; // quoted variable
+ if (lazy || (buffer[varname] >= '0' && buffer[varname] <= '9'))
+ {
+ // replace later
+ continue;
+ }
+ StreamBuffer value;
+ if (!globalSettings.replaceVariable(value, buffer(token)))
+ return false;
+ buffer.replace(token, buffer.length(), value);
+ continue;
+ }
+ if (c == '{' || c == '=')
+ {
+ errorMsg("Unexpected '%c' (missing ';' or '\"' ?)\n", c);
+ return false;
+ }
+ if (strchr (";}", c))
+ {
+ buffer.truncate(-1);
+ if (c != ';')
+ {
+ // let's be generous with missing ';' before '}'
+ ungetc (c, file);
+ }
+ return true;
+ }
+ }
+}
+
+// tools (static member functions)
+
+const char* StreamProtocolParser::
+printString(StreamBuffer& buffer, const char* s)
+{
+ while (*s)
+ {
+ switch (*s)
+ {
+ case esc:
+ buffer.printf("\\x%02x", (*++s) & 0xff);
+ break;
+ case '\r':
+ buffer.append("\\r");
+ break;
+ case '\n':
+ buffer.append("\\n");
+ break;
+ case skip:
+ buffer.append("\\?");
+ break;
+ case '"':
+ buffer.append("\\\"");
+ break;
+ case '\\':
+ buffer.append("\\\\");
+ break;
+ case format_field:
+ buffer.printf("%%(%s)", ++s);
+ while (*s++);
+ s += extract(s); // skip fieldaddress
+ goto format;
+ case format:
+ buffer.append('%');
+ s++;
+format: {
+ s = printString(buffer, s);
+ const StreamFormat& f = extract(s);
+ s += f.infolen;
+ }
+ continue;
+ default:
+ if ((*s & 0x7f) < 0x20 || (*s & 0x7f) == 0x7f)
+ buffer.printf("\\x%02x", *s & 0xff);
+ else
+ buffer.append(*s);
+ }
+ ++s;
+ }
+ return ++s;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// StreamProtocolParser::Protocol::Variable
+
+StreamProtocolParser::Protocol::Variable::
+Variable(const char* name, int line, size_t startsize)
+ : name(name), value(startsize), line(line)
+{
+ next = NULL;
+ used = false;
+}
+
+StreamProtocolParser::Protocol::Variable::
+Variable(const Variable& v)
+ : name(v.name), value(v.value)
+{
+ line = v.line;
+ used = v.used;
+ next = NULL;
+}
+
+StreamProtocolParser::Protocol::Variable::
+~Variable()
+{
+ delete next;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// StreamProtocolParser::Protocol
+
+// for global settings
+StreamProtocolParser::Protocol::
+Protocol(const char* filename)
+ : filename(filename)
+{
+ line = 0;
+ next = NULL;
+ variables = new Variable(NULL, 0, 500);
+ commands = &variables->value;
+}
+
+// make a deep copy
+StreamProtocolParser::Protocol::
+Protocol(const Protocol& p, StreamBuffer& name, int _line)
+ : protocolname(name), filename(p.filename)
+{
+ next = NULL;
+ // copy all variables
+ Variable* pV;
+ Variable** ppNewV = &variables;
+ line = _line ? _line : p.line;
+ debug("new Protocol(name=\"%s\", line=%d)\n", name(), line);
+ for (pV = p.variables; pV; pV = pV->next)
+ {
+ *ppNewV = new Variable(*pV);
+ ppNewV = &(*ppNewV)->next;
+ }
+ commands = &variables->value;
+ if (line) variables->line = line;
+ // get parameters from name
+ memset(parameter, 0, sizeof(parameter));
+ int i;
+ const char* nextparameter;
+ parameter[0] = protocolname();
+ for (i = 0; i < 10; i++)
+ {
+ debug("StreamProtocolParser::Protocol::Protocol $%d=\"%s\"\n",
+ i, parameter[i]);
+ nextparameter = parameter[i] + strlen(parameter[i]) + 1;
+ if (nextparameter > name.length() + parameter[0]) break;
+ parameter[i+1] = nextparameter;
+ }
+}
+
+StreamProtocolParser::Protocol::
+~Protocol()
+{
+ delete variables;
+ 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()
+{
+ Variable* pV;
+ if (protocolname) printf(" Protocol %s\n", protocolname.expand()());
+ printf(" Variables:\n");
+ for (pV = variables->next; pV; pV = pV->next) // 1st var is commands
+ {
+ if (pV->name[0] != '@')
+ {
+ printf(" %s = %s;\n", pV->name.expand()(), pV->value.expand()());
+ }
+ }
+ printf(" Handlers:\n");
+ for (pV = variables->next; pV; pV = pV->next)
+ {
+ if (pV->name[0] == '@')
+ {
+ printf(" %s {%s}\n", pV->name.expand()(), pV->value.expand()());
+ }
+ }
+ printf(" Commands:\n");
+ printf(" { %s }\n", commands->expand()());
+}
+
+StreamBuffer* StreamProtocolParser::Protocol::
+createVariable(const char* name, int linenr)
+{
+ Variable** ppV;
+ for (ppV = &variables; *ppV; ppV = &(*ppV)->next)
+ {
+ if ((*ppV)->name.equals(name))
+ {
+ (*ppV)->line = linenr;
+ return &(*ppV)->value;
+ }
+ }
+ *ppV = new Variable(name, linenr);
+ return &(*ppV)->value;
+}
+
+const StreamProtocolParser::Protocol::Variable*
+ StreamProtocolParser::Protocol::
+getVariable(const char* name)
+{
+ Variable* pV;
+
+ for (pV = variables; pV; pV = pV->next)
+ {
+ if (pV->name.equals(name))
+ {
+ pV->used = true;
+ return pV;
+ }
+ }
+ return NULL;
+}
+
+bool StreamProtocolParser::Protocol::
+getNumberVariable(const char* varname, unsigned long& value, unsigned long max)
+{
+ const Variable* pvar = getVariable(varname);
+ if (!pvar) return true;
+ const char* source = pvar->value();
+ if (!compileNumber(value, source, max))
+ {
+ int linenr = getLineNumber(source);
+ errorMsg(linenr, "in variable %s\n", varname);
+ return false;
+ }
+ if (source != pvar->value.end())
+ {
+ errorMsg(getLineNumber(source),
+ "Garbage in variable '%s' after numeric value %ld: %s\n",
+ varname, value, source);
+ return false;
+ }
+ return true;
+}
+
+bool StreamProtocolParser::Protocol::
+getEnumVariable(const char* varname, unsigned short& value, const char** enumstrings)
+{
+ const Variable* pvar = getVariable(varname);
+ if (!pvar) return true;
+ for (value = 0; enumstrings[value]; value++)
+ {
+ if (pvar->value.equals(enumstrings[value]))
+ {
+ return true;
+ }
+ }
+ error("Value '%s' must be one of", pvar->value());
+ for (value = 0; enumstrings[value]; value++)
+ {
+ error("%s '%s'", value ? " or" : "", enumstrings[value]);
+ }
+ error("\n"
+ "in variable '%s' in protocol file '%s' line %d\n",
+ varname, filename(), getLineNumber(pvar->value()));
+ return false;
+}
+
+bool StreamProtocolParser::Protocol::
+getStringVariable(const char* varname, StreamBuffer& value, bool* defined)
+{
+ const Variable* pvar = getVariable(varname);
+ if (!pvar) return true;
+ if (defined) *defined = true;
+ const StreamBuffer* pvalue = &pvar->value;
+ const char* source = (*pvalue)();
+ value.clear();
+ if (!compileString(value, source))
+ {
+ error("in string variable '%s' in protocol file '%s' line %d\n",
+ varname, filename(), getLineNumber(source));
+ debug("%s = %s\n", varname, pvalue->expand()());
+ return false;
+ }
+ if (source != pvalue->end())
+ {
+ debug("%s = %s\n", varname, pvalue->expand()());
+ debug(" => %s\n", value.expand()());
+ error("INTERNAL ERROR after '%s': source = %p != %p\n",
+ varname, source, pvalue->end());
+ return false;
+ }
+ return true;
+}
+
+bool StreamProtocolParser::Protocol::
+getCommands(const char* handlername,StreamBuffer& code, Client* client)
+{
+ const Variable* pvar = getVariable(handlername);
+ if (!pvar) return true;
+ if (!pvar->value) return true;
+ code.clear();
+ const char* source = pvar->value();
+ debug("StreamProtocolParser::Protocol::getCommands"
+ "(handlername=\"%s\", client=\"%s\"): source=%s\n",
+ handlername, client->name(), pvar->value.expand()());
+ if (!compileCommands(code, source, client))
+ {
+ if (handlername)
+ {
+ errorMsg(pvar->line,
+ "in handler '%s'\n", handlername);
+ errorMsg(variables->line,
+ "used by protocol '%s'\n", protocolname());
+ return false;
+ }
+ errorMsg(pvar->line, "in protocol '%s'\n", protocolname());
+ return false;
+ }
+ debug("commands %s: %s\n", handlername, pvar->value.expand()());
+ debug("compiled to: %s\n", code.expand()());
+ return true;
+}
+
+bool StreamProtocolParser::Protocol::
+replaceVariable(StreamBuffer& buffer, const char* varname)
+{
+ debug("StreamProtocolParser::Protocol::replaceVariable %s\n", varname);
+ bool quoted = false;
+ if (*++varname == '"')
+ {
+ varname++;
+ quoted = true;
+ }
+ int linenr = getLineNumber(varname);
+ if (*varname >= '0' && *varname <= '9')
+ {
+ const char* p = parameter[*varname-'0'];
+ if (!p)
+ {
+ errorMsg(linenr,
+ "Missing value for parameter $%c\n", *varname);
+ return false;
+ }
+ if (!quoted)
+ {
+ buffer.append(p).append('\0');
+ buffer.append(&linenr, sizeof(linenr));
+ return true;
+ }
+ buffer.append('"');
+ bool escaped = false;
+ while (*p)
+ {
+ if (*p == '"' && !escaped) buffer.append('\\');
+ if (escaped) escaped = false;
+ else if (*p == '\\') escaped = true;
+ buffer.append(*p++);
+ }
+ buffer.append('"').append('\0');
+ buffer.append(&linenr, sizeof(linenr));
+ return true;
+ }
+ const Variable* v = getVariable(varname);
+ if (!v)
+ {
+ errorMsg(linenr,
+ "Undefined variable '%s' referenced\n",
+ varname);
+ return false;
+ }
+ if (!quoted)
+ {
+ buffer.append(v->value);
+ return true;
+ }
+ // quoted
+ buffer.append('"');
+ long i;
+ bool escaped = false;
+ for (i = 0; i < v->value.length(); i++)
+ {
+ char c = v->value[i];
+ if (c == '"' && !escaped) buffer.append('\\');
+ if (c == 0 && !escaped)
+ {
+ // skip line info of token
+ i += sizeof(linenr);
+ continue;
+ }
+ if (escaped) escaped = false;
+ else if (c == '\\') escaped = true;
+ buffer.append(c);
+ }
+ buffer.append('"').append('\0');
+ linenr = v->line;
+ buffer.append(&linenr, sizeof(linenr));
+ return true;
+}
+
+bool StreamProtocolParser::Protocol::
+compileNumber(unsigned long& number, const char*& source, unsigned long max)
+{
+ char* end;
+ unsigned long n;
+ StreamBuffer buffer;
+
+ debug("StreamProtocolParser::Protocol::compileNumber source=\"%s\"\n",
+ source);
+ while (*source == '$' || (*source >= '0' && *source <= '9'))
+ {
+ debug("StreamProtocolParser::Protocol::compileNumber *source=%u source=\"%s\"\n",
+ *source, source);
+ if (*source == '$')
+ {
+ if(!replaceVariable(buffer, source)) return false;
+ debug("buffer=%s\n", buffer.expand()());
+ buffer.truncate(-1-(int)sizeof(int));
+ }
+ else
+ {
+ buffer.append(source);
+ }
+ source += strlen(source)+1+sizeof(int); // skip eos + line
+ };
+ n = strtoul(buffer(), &end, 0);
+ if (end == buffer())
+ {
+ debug("StreamProtocolParser::Protocol::compileNumber: %s\n",
+ buffer.expand()());
+ errorMsg(getLineNumber(source),
+ "Unsigned numeric value expected: %s\n", buffer());
+ return false;
+ }
+ if (*end)
+ {
+ debug("StreamProtocolParser::Protocol::compileNumber: %s\n",
+ buffer.expand()());
+ errorMsg(getLineNumber(source),
+ "Garbage after numeric value: %s\n", buffer());
+ return false;
+ }
+ if (n < 0 || n > max)
+ {
+ debug("StreamProtocolParser::Protocol::compileNumber: %s\n",
+ buffer.expand()());
+ errorMsg(getLineNumber(source),
+ "Value %s out of range [0...%ld]\n", buffer(), max);
+ return false;
+ }
+ number = n;
+ debug("StreamProtocolParser::Protocol::compileNumber %s = %ld\n",
+ buffer(), n);
+ return true;
+}
+
+// read a string (quoted or numerical byte values) from source code line
+// and put it to buffer, replacing all variable references and code formats
+
+bool StreamProtocolParser::Protocol::
+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;
+ line = getLineNumber(source);
+
+ debug("StreamProtocolParser::Protocol::compileString "
+ "line %d source=\"%s\"\n",
+ line, source);
+
+ // coding is done in two steps:
+ // 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
+
+ while (1)
+ {
+ // 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++)
+ {
+ 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))
+ {
+ p = buffer(pos);
+ formatbuffer.clear();
+ printString(formatbuffer, p);
+ errorMsg(line, "in format string: \"%s\"\n",
+ formatbuffer());
+ return false;
+ }
+ 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;
+ }
+ if (!*source) break;
+ numFormats = 0;
+ line = newline;
+ }
+ // this is step 1: coding the string
+ if ((*source & 0x7f) < 0x20)
+ {
+ error("Unexpected byte %#04x\n", *source & 0xFF);
+ return false;
+ }
+ if (escaped) // char after \ in quoted text
+ {
+ escaped = false;
+ unsigned int temp;
+ switch (*source)
+ {
+ case '$': // can't be: readToken would have made a token from this
+ errorMsg(line,
+ "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;
+ case 'a':
+ buffer.append(7);
+ break;
+ case 'b':
+ buffer.append(8);
+ break;
+ case 't':
+ buffer.append(9);
+ break;
+ case 'n':
+ buffer.append('\n');
+ break;
+ case 'r':
+ buffer.append('\r');
+ break;
+ case 'e':
+ buffer.append(esc).append(esc);
+ break;
+ case '0': // octal numbers (max 3 digits after 0)
+ sscanf (source, "%3o%n", &temp, &n);
+ if (temp > 0xFF)
+ {
+ errorMsg(line,
+ "Octal source %#o does not fit in byte: %s\n",
+ temp, source-1);
+ return false;
+ }
+ if (formatType != NoFormat &&
+ (temp < last_function_code || temp == esc))
+ {
+ 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",
+ source-1);
+ return false;
+ }
+ if (formatType != NoFormat &&
+ (temp < last_function_code || temp == esc))
+ {
+ buffer.append(esc);
+ }
+ buffer.append(temp);
+ source += n+1;
+ continue;
+ case '1': // decimal numbers (max 3 digits)
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ sscanf (source, "%3u%n", &temp, &n);
+ if (temp > 0xFF)
+ {
+ errorMsg(line,
+ "Decimal source %d does not fit in byte: %s\n",
+ temp, source-1);
+ return false;
+ }
+ if (formatType != NoFormat &&
+ (temp < last_function_code || temp == esc))
+ {
+ buffer.append(esc);
+ }
+ buffer.append(temp);
+ source += n;
+ continue;
+ default: // escaped literals
+ buffer.append(esc).append(*source);
+ }
+ source++;
+ continue;
+ }
+ if (quoted) // look for ending quotes, escapes, and formats
+ {
+ 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
+ {
+ source += 1+sizeof(int); // skip line number
+ quoted = false;
+ break;
+ }
+ default:
+ buffer.append(*source); // literal character
+ }
+ source++;
+ continue;
+ }
+ switch (*source) // tokens other than quoted string
+ {
+ const char* p;
+ case '$': // parameter or variable
+ {
+ StreamBuffer value;
+ if (!replaceVariable(value, source)) return false;
+ source += strlen(source)+1+sizeof(int);
+ p = value();
+ int saveline = line;
+ if (!compileString(buffer, p, formatType, client))
+ return false;
+ line = saveline;
+ continue;
+ }
+ case '\'': // starting single quotes
+ case '"': // starting double quotes
+ quoted = *source++;
+ continue;
+// case '}':
+// case ')':
+// buffer.append(eos);
+ case ' ': // skip whitespace
+ case ',': // treat comma as whitespace
+ source++;
+ continue;
+ }
+ // try numeric byte value
+ char* p;
+ int temp = strtol(source, &p, 0);
+ if (p != source)
+ {
+ if (*p != 0)
+ {
+ errorMsg(line,
+ "Garbage after numeric source: %s", source);
+ return false;
+ }
+ if (temp > 0xFF || temp < -0x80)
+ {
+ errorMsg(line,
+ "Value %s does not fit in byte\n", source);
+ return false;
+ }
+ if (formatType != NoFormat &&
+ (temp < last_function_code || temp == esc))
+ {
+ buffer.append(esc);
+ }
+ buffer.append(temp);
+ source = p+1+sizeof(int);
+ continue;
+ }
+ // try constant token
+ struct {const char* name; char code;} codes [] =
+ {
+ {"skip", skip},
+ {"?", skip},
+ {"eot", 4 },
+ {"ack", 6 },
+ {"bel", 7 },
+ {"bs", 8 },
+ {"ht", 9 },
+ {"tab", 9 },
+ {"lf", '\n' },
+ {"nl", '\n' },
+ {"cr", '\r' },
+ {"esc", esc },
+ {"del", 0x7f}
+ };
+ size_t i;
+ char c = 0;
+ for (i = 0; i < sizeof(codes)/sizeof(*codes); i++)
+ {
+ if (strcmp(source, codes[i].name) == 0)
+ {
+ c = codes[i].code;
+ if (c == skip && formatType != ScanFormat)
+ {
+ errorMsg(line,
+ "Use of '%s' only allowed in input formats\n",
+ source);
+ return false;
+ }
+ if (formatType != NoFormat &&
+ (temp < last_function_code || temp == esc))
+ {
+ buffer.append(esc);
+ }
+ buffer.append(c);
+ source += strlen(source)+1+sizeof(int);
+ break;
+ }
+ }
+ if (c) continue;
+ // source may contain a function name
+ errorMsg(line,
+ "Unexpected word: %s\n", source);
+ return false;
+ }
+ debug("StreamProtocolParser::Protocol::compileString buffer=%s\n", buffer.expand()());
+ return true;
+}
+
+bool StreamProtocolParser::Protocol::
+compileFormat(StreamBuffer& buffer, const char*& formatstr,
+ FormatType formatType, Client* client)
+{
+/*
+ formatstr := '%' ['(' field ')'] [flags] [width] ['.' prec] conv [extra]
+ field := string
+ flags := '-' | '+' | ' ' | '#' | '0' | '*'
+ width := integer
+ prec := integer
+ conv := character
+ extra := string
+ only conv is required all other parts are optional.
+
+ compiles to:
+ formatstr StreamFormat [info ]
+ or:
+ field addrLength AddressStructure formatstr StreamFormat [info ]
+*/
+ const char* source = formatstr;
+ StreamFormat streamFormat;
+ int fieldname = 0;
+ // look for fieldname
+ if (source[1] == '(')
+ {
+ buffer.append(format_field);
+ if (!client)
+ {
+ errorMsg(line,
+ "Using fieldname is not possible in this context\n");
+ return false;
+ }
+ const char* fieldnameEnd = strchr(source+=2, ')');
+ if (!fieldnameEnd)
+ {
+ errorMsg(line,
+ "Missing ')' after field name\n");
+ return false;
+ }
+ // add fieldname for debug purpose
+ fieldname=buffer.length();
+ buffer.append(source, fieldnameEnd - source).append(eos);
+ debug("StreamProtocolParser::Protocol::compileFormat: fieldname='%s'\n",
+ buffer(fieldname));
+ StreamBuffer fieldAddress;
+ if (!client->getFieldAddress(buffer(fieldname), fieldAddress))
+ {
+ errorMsg(line,
+ "Field '%s' not found\n", buffer(fieldname));
+ return false;
+ }
+ source = fieldnameEnd;
+ unsigned short length = (unsigned short)fieldAddress.length();
+ buffer.append(&length, sizeof(length));
+ buffer.append(fieldAddress);
+ }
+ else
+ {
+ 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);
+ if (!type)
+ {
+ // parsing failed
+ return false;
+ }
+ if (type < long_format && type > pseudo_format)
+ {
+ errorMsg(line,
+ "Illegal format type %d returned from '%%%c' converter\n",
+ type, streamFormat.conv);
+ return false;
+ }
+ if (type == pseudo_format && fieldname)
+ {
+ errorMsg(line,
+ "Fieldname not allowed with pseudo format: '%%(%s)%c'\n",
+ buffer(fieldname), streamFormat.conv);
+ return false;
+ }
+ streamFormat.type = static_cast(type);
+ if (infoString && infoString[-1] != eos)
+ {
+ // terminate if necessary
+ infoString.append(eos);
+ }
+ streamFormat.infolen = (unsigned short)infoString.length();
+ // add formatstr for debug purpose
+ buffer.append(formatstart, source-formatstart).append(eos);
+
+#ifndef NO_TEMPORARY
+ debug("StreamProtocolParser::Protocol::compileFormat: formatstring=\"%s\"\n",
+ StreamBuffer(formatstart, source-formatstart).expand()());
+#endif
+
+ // add streamFormat structure and info
+ buffer.append(&streamFormat, sizeof(streamFormat));
+ buffer.append(infoString);
+
+ debug("StreamProtocolParser::Protocol::compileFormat: format.type=%s, infolen=%d\n",
+ StreamFormatTypeStr[streamFormat.type], streamFormat.infolen);
+ formatstr = source; // move pointer after parsed format
+ return true;
+}
+
+bool StreamProtocolParser::Protocol::
+compileCommands(StreamBuffer& buffer, const char*& source, Client* client)
+{
+ const char* command;
+ const char* args;
+
+ while (*source)
+ {
+ command = source;
+ args = source + strlen(source)+1+sizeof(int);
+ if (!client->compileCommand(this, buffer, command, args))
+ {
+ errorMsg(getLineNumber(source),
+ "in command '%s'\n", command);
+ return false;
+ }
+ if (*args)
+ {
+ errorMsg(getLineNumber(source),
+ "Garbage after '%s' command: '%s'\n",
+ command, args);
+ return false;
+ }
+ source = args + 1;
+ }
+ buffer.append(eos);
+ return true;
+}
+
+bool StreamProtocolParser::Protocol::
+checkUnused()
+{
+ Variable* pV;
+
+ for (pV = variables; pV; pV = pV->next)
+ {
+ if (!pV->used)
+ {
+ if (pV->name[0] == '@')
+ {
+ error("Unknown handler %s defined in protocol file '%s' line %d\n",
+ pV->name(), filename(), pV->line);
+ return false;
+ }
+ debug("Unused variable %s in protocol file '%s' line %d\n",
+ pV->name(), filename(), pV->line);
+ }
+ }
+ return true;
+}
diff --git a/src/StreamProtocol.h b/src/StreamProtocol.h
new file mode 100644
index 0000000..f5d6934
--- /dev/null
+++ b/src/StreamProtocol.h
@@ -0,0 +1,164 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is the protocol parser 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. *
+* *
+***************************************************************/
+
+#ifndef StreamProtocol_h
+#define StreamProtocol_h
+
+#include "StreamBuffer.h"
+#include
+
+enum FormatType {NoFormat, ScanFormat, PrintFormat};
+
+class StreamProtocolParser
+{
+public:
+
+ enum Codes
+ {
+ eos = 0, skip, format, format_field, last_function_code
+ };
+
+ class Client;
+
+ class Protocol
+ {
+ class Variable;
+
+ friend class StreamProtocolParser;
+
+ private:
+ Protocol* next;
+ Variable* variables;
+ const StreamBuffer protocolname;
+ StreamBuffer* commands;
+ int line;
+ const char* parameter[10];
+
+ Protocol(const char* filename);
+ Protocol(const Protocol& p, StreamBuffer& name, int line);
+ StreamBuffer* createVariable(const char* name, int line);
+ bool compileFormat(StreamBuffer&, const char*& source,
+ FormatType, Client*);
+ bool compileCommands(StreamBuffer&, const char*& source, Client*);
+ bool replaceVariable(StreamBuffer&, const char* varname);
+ const Variable* getVariable(const char* name);
+
+ public:
+
+ const StreamBuffer filename;
+
+ bool getNumberVariable(const char* varname, unsigned long& value,
+ unsigned long max = 0xFFFFFFFF);
+ bool getEnumVariable(const char* varname, unsigned short& value,
+ const char ** enumstrings);
+ bool getStringVariable(const char* varname,StreamBuffer& value, bool* defined = NULL);
+ bool getCommands(const char* handlername, StreamBuffer& code, Client*);
+ bool compileNumber(unsigned long& number, const char*& source,
+ unsigned long max = 0xFFFFFFFF);
+ 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();
+ };
+
+ class Client
+ {
+ friend class StreamProtocolParser::Protocol;
+ virtual bool compileCommand(Protocol*, StreamBuffer& buffer,
+ const char* command, const char*& args) = 0;
+ virtual bool getFieldAddress(const char* fieldname,
+ StreamBuffer& address) = 0;
+ virtual const char* name() = 0;
+ public:
+ virtual ~Client();
+ };
+
+private:
+ StreamBuffer filename;
+ FILE* file;
+ int line;
+ int quote;
+ Protocol globalSettings;
+ Protocol* protocols;
+ StreamProtocolParser* next;
+ static StreamProtocolParser* parsers;
+ bool valid;
+
+ StreamProtocolParser(FILE* file, const char* filename);
+ Protocol* getProtocol(const StreamBuffer& protocolAndParams);
+ bool isGlobalContext(const StreamBuffer* commands);
+ bool isHandlerContext(Protocol&, const StreamBuffer* commands);
+ static StreamProtocolParser* readFile(const char* file);
+ bool parseProtocol(Protocol&, StreamBuffer* commands);
+ int readChar();
+ bool readToken(StreamBuffer& buffer,
+ 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
+
+public:
+ static Protocol* getProtocol(const char* file,
+ const StreamBuffer& protocolAndParams);
+ static void free();
+ static const char* path;
+ static const char* printString(StreamBuffer&, const char* string);
+ void report();
+};
+
+inline int getLineNumber(const char* s)
+{
+ int l;
+ memcpy (&l, s+strlen(s)+1, sizeof(int));
+ return l;
+}
+
+template
+inline const T extract(const char*& string)
+{
+ T p;
+ memcpy (&p, string, sizeof(T));
+ string += sizeof(T);
+ return p;
+}
+
+/*
+
+API functions:
+
+NAME: getProtocol()
+PURPOSE: read protocol from file, create parser if necessary
+RETURNS: a copy of a protocol that must be deleted by the caller
+SIDEEFFECTS: file IO, memory allocation for parser
+
+NAME: free()
+PURPOSE: free all parser resources allocated by getProtocol()
+Call this function once after the last getProtocol() to clean up.
+
+*/
+
+#endif
diff --git a/src/StreamVersion.c b/src/StreamVersion.c
new file mode 100644
index 0000000..8795910
--- /dev/null
+++ b/src/StreamVersion.c
@@ -0,0 +1,28 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This provides a version string 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"
+
+#define STR2(x) #x
+#define STR(x) STR2(x)
+const char StreamVersion [] =
+ "StreamDevice " STR(STREAM_MAJOR) "." STR(STREAM_MINOR)
+ " built " __DATE__ " " __TIME__;
+
diff --git a/src/devStream.h b/src/devStream.h
new file mode 100644
index 0000000..d56bff6
--- /dev/null
+++ b/src/devStream.h
@@ -0,0 +1,115 @@
+/***************************************************************
+* StreamDevice Support *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* This is the header for the EPICS interface to 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. *
+* *
+***************************************************************/
+
+#ifndef devStream_h
+#define devStream_h
+
+#define STREAM_MAJOR 2
+#define STREAM_MINOR 2
+
+#if defined(__vxworks) || defined(vxWorks)
+#include
+#endif
+
+#ifndef OK
+#define OK 0
+#endif
+
+#ifndef ERROR
+#define ERROR -1
+#endif
+
+#define DO_NOT_CONVERT 2
+#define INIT_RUN (!interruptAccept)
+
+#include
+#if (EPICS_VERSION == 3 && EPICS_REVISION == 14)
+#define EPICS_3_14
+#endif
+
+#if defined(__cplusplus) && !defined(EPICS_3_14)
+extern "C" {
+#endif
+
+#include
+#include
+#include
+#include
+/* #include */
+#include
+
+#if defined(__cplusplus) && !defined(EPICS_3_14)
+}
+#endif
+
+
+typedef const struct format_s {
+ unsigned char type;
+ const struct StreamFormat* priv;
+} format_t;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef _WIN32
+__declspec(dllimport)
+#endif
+extern FILE* StreamDebugFile;
+
+extern const char StreamVersion [];
+
+typedef long (*streamIoFunction) (dbCommon*, format_t*);
+
+long streamInit(int after);
+long streamInitRecord(dbCommon *record, 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,
+ void*, size_t maxStringSize);
+
+/* backward compatibility stuff */
+#define devStreamIoFunction streamIoFunction
+#define devStreamInit streamInit
+#define devStreamInitRecord streamInitRecord
+#define devStreamReport streamReport
+#define devStreamRead streamReadWrite
+#define devStreamWrite streamReadWrite
+#define devStreamGetIointInfo streamGetIointInfo
+#define devStreamPrintf streamPrintf
+#define devStreamPrintSep(record) (0)
+#define devStreamScanSep streamScanSep
+#define devStreamScanf(record, format, value) \
+ streamScanfN(record, format, value, MAX_STRING_SIZE)
+#define streamScanf(record, format, value) \
+ streamScanfN(record, format, value, MAX_STRING_SIZE)
+#define streamRead streamReadWrite
+#define streamWrite streamReadWrite
+#define streamReport NULL
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/devaaiStream.c b/src/devaaiStream.c
new file mode 100644
index 0000000..b2a76cb
--- /dev/null
+++ b/src/devaaiStream.c
@@ -0,0 +1,302 @@
+/***************************************************************
+* StreamDevice record interface for aai records *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (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
+#include
+#include
+#include
+#include
+
+static long readData (dbCommon *record, format_t *format)
+{
+ aaiRecord *aai = (aaiRecord *) record;
+ double dval;
+ long lval;
+
+ for (aai->nord = 0; aai->nord < aai->nelm; aai->nord++)
+ {
+ switch (format->type)
+ {
+ case DBF_DOUBLE:
+ {
+ if (streamScanf (record, format, &dval) != OK)
+ {
+ return aai->nord ? OK : ERROR;
+ }
+ switch (aai->ftvl)
+ {
+ case DBF_DOUBLE:
+ ((double *)aai->bptr)[aai->nord] = dval;
+ break;
+ case DBF_FLOAT:
+ ((float *)aai->bptr)[aai->nord] = (float)dval;
+ break;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "readData %s: can't convert from double to %s\n",
+ record->name, pamapdbfType[aai->ftvl].strvalue);
+ return ERROR;
+ }
+ break;
+ }
+ case DBF_LONG:
+ case DBF_ENUM:
+ {
+ if (streamScanf (record, format, &lval) != OK)
+ {
+ return aai->nord ? OK : ERROR;
+ }
+ switch (aai->ftvl)
+ {
+ case DBF_DOUBLE:
+ ((double *)aai->bptr)[aai->nord] = lval;
+ break;
+ case DBF_FLOAT:
+ ((float *)aai->bptr)[aai->nord] = (float)lval;
+ break;
+ case DBF_LONG:
+ case DBF_ULONG:
+ ((long *)aai->bptr)[aai->nord] = lval;
+ break;
+ case DBF_SHORT:
+ case DBF_USHORT:
+ case DBF_ENUM:
+ ((short *)aai->bptr)[aai->nord] = (short)lval;
+ break;
+ case DBF_CHAR:
+ case DBF_UCHAR:
+ ((char *)aai->bptr)[aai->nord] = (char)lval;
+ break;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "readData %s: can't convert from long to %s\n",
+ record->name, pamapdbfType[aai->ftvl].strvalue);
+ return ERROR;
+ }
+ break;
+ }
+ case DBF_STRING:
+ {
+ switch (aai->ftvl)
+ {
+ case DBF_STRING:
+ if (streamScanfN (record, format,
+ (char *)aai->bptr + aai->nord * MAX_STRING_SIZE,
+ MAX_STRING_SIZE) != OK)
+ {
+ return aai->nord ? OK : ERROR;
+ }
+ break;
+ case DBF_CHAR:
+ case DBF_UCHAR:
+ memset (aai->bptr, 0, aai->nelm);
+ aai->nord = 0;
+ if (streamScanfN (record, format,
+ (char *)aai->bptr, aai->nelm) != OK)
+ {
+ return ERROR;
+ }
+ ((char*)aai->bptr)[aai->nelm] = 0;
+ for (lval = aai->nelm;
+ lval >= 0 && ((char*)aai->bptr)[lval] == 0;
+ lval--);
+ aai->nord = lval+1;
+ return OK;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "readData %s: can't convert from string to %s\n",
+ record->name, pamapdbfType[aai->ftvl].strvalue);
+ return ERROR;
+ }
+ break;
+ }
+ default:
+ {
+ errlogSevPrintf (errlogMajor,
+ "readData %s: can't convert from %s to %s\n",
+ record->name, pamapdbfType[format->type].strvalue,
+ pamapdbfType[aai->ftvl].strvalue);
+ return ERROR;
+ }
+ }
+ }
+ return OK;
+}
+
+static long writeData (dbCommon *record, format_t *format)
+{
+ aaiRecord *aai = (aaiRecord *) record;
+ double dval;
+ long lval;
+ unsigned long nowd;
+
+ for (nowd = 0; nowd < aai->nord; nowd++)
+ {
+ switch (format->type)
+ {
+ case DBF_DOUBLE:
+ {
+ switch (aai->ftvl)
+ {
+ case DBF_DOUBLE:
+ dval = ((double *)aai->bptr)[nowd];
+ break;
+ case DBF_FLOAT:
+ dval = ((float *)aai->bptr)[nowd];
+ break;
+ case DBF_LONG:
+ dval = ((long *)aai->bptr)[nowd];
+ break;
+ case DBF_ULONG:
+ dval = ((unsigned long *)aai->bptr)[nowd];
+ break;
+ case DBF_SHORT:
+ dval = ((short *)aai->bptr)[nowd];
+ break;
+ case DBF_USHORT:
+ case DBF_ENUM:
+ dval = ((unsigned short *)aai->bptr)[nowd];
+ break;
+ case DBF_CHAR:
+ dval = ((char *)aai->bptr)[nowd];
+ break;
+ case DBF_UCHAR:
+ dval = ((unsigned char *)aai->bptr)[nowd];
+ break;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "writeData %s: can't convert from %s to double\n",
+ record->name, pamapdbfType[aai->ftvl].strvalue);
+ return ERROR;
+ }
+ if (streamPrintf (record, format, dval))
+ return ERROR;
+ break;
+ }
+ case DBF_LONG:
+ case DBF_ENUM:
+ {
+ switch (aai->ftvl)
+ {
+ case DBF_LONG:
+ case DBF_ULONG:
+ lval = ((long *)aai->bptr)[nowd];
+ break;
+ case DBF_SHORT:
+ lval = ((short *)aai->bptr)[nowd];
+ break;
+ case DBF_USHORT:
+ case DBF_ENUM:
+ lval = ((unsigned short *)aai->bptr)[nowd];
+ break;
+ case DBF_CHAR:
+ lval = ((char *)aai->bptr)[nowd];
+ break;
+ case DBF_UCHAR:
+ lval = ((unsigned char *)aai->bptr)[nowd];
+ break;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "writeData %s: can't convert from %s to long\n",
+ record->name, pamapdbfType[aai->ftvl].strvalue);
+ return ERROR;
+ }
+ if (streamPrintf (record, format, lval))
+ return ERROR;
+ break;
+ }
+ case DBF_STRING:
+ {
+ switch (aai->ftvl)
+ {
+ case DBF_STRING:
+ if (streamPrintf (record, format,
+ ((char *)aai->bptr) + nowd * MAX_STRING_SIZE))
+ return ERROR;
+ break;
+ case DBF_CHAR:
+ case DBF_UCHAR:
+ /* print aai as a null-terminated string */
+ if (aai->nord < aai->nelm)
+ {
+ ((char *)aai->bptr)[aai->nord] = 0;
+ }
+ else
+ {
+ ((char *)aai->bptr)[aai->nelm-1] = 0;
+ }
+ if (streamPrintf (record, format,
+ ((char *)aai->bptr)))
+ return ERROR;
+ return OK;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "writeData %s: can't convert from %s to string\n",
+ record->name, pamapdbfType[aai->ftvl].strvalue);
+ return ERROR;
+ }
+ break;
+ }
+ default:
+ {
+ errlogSevPrintf (errlogFatal,
+ "writeData %s: can't convert from %s to %s\n",
+ record->name, pamapdbfType[aai->ftvl].strvalue,
+ pamapdbfType[format->type].strvalue);
+ return ERROR;
+ }
+ }
+ }
+ return OK;
+}
+
+static long initRecord (dbCommon *record)
+{
+ static const int typesize[] = {MAX_STRING_SIZE,1,1,2,2,4,4,4,8,2};
+ aaiRecord *aai = (aaiRecord *) record;
+
+ aai->bptr = calloc(aai->nelm, typesize[aai->ftvl]);
+ if (aai->bptr == NULL)
+ {
+ errlogSevPrintf (errlogFatal,
+ "initRecord %s: can't allocate memory for data array\n",
+ record->name);
+ return ERROR;
+ }
+ return streamInitRecord (record, &aai->inp, readData, writeData);
+}
+
+struct {
+ long number;
+ DEVSUPFUN report;
+ DEVSUPFUN init;
+ DEVSUPFUN init_record;
+ DEVSUPFUN get_ioint_info;
+ DEVSUPFUN read;
+} devaaiStream = {
+ 5,
+ streamReport,
+ streamInit,
+ initRecord,
+ streamGetIointInfo,
+ streamRead
+};
+
+epicsExportAddress(dset,devaaiStream);
diff --git a/src/devaaoStream.c b/src/devaaoStream.c
new file mode 100644
index 0000000..eba6eff
--- /dev/null
+++ b/src/devaaoStream.c
@@ -0,0 +1,302 @@
+/***************************************************************
+* StreamDevice record interface for aao records *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (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
+#include
+#include
+#include
+#include
+
+static long readData (dbCommon *record, format_t *format)
+{
+ aaoRecord *aao = (aaoRecord *) record;
+ double dval;
+ long lval;
+
+ for (aao->nord = 0; aao->nord < aao->nelm; aao->nord++)
+ {
+ switch (format->type)
+ {
+ case DBF_DOUBLE:
+ {
+ if (streamScanf (record, format, &dval) != OK)
+ {
+ return aao->nord ? OK : ERROR;
+ }
+ switch (aao->ftvl)
+ {
+ case DBF_DOUBLE:
+ ((double *)aao->bptr)[aao->nord] = dval;
+ break;
+ case DBF_FLOAT:
+ ((float *)aao->bptr)[aao->nord] = (float)dval;
+ break;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "readData %s: can't convert from double to %s\n",
+ record->name, pamapdbfType[aao->ftvl].strvalue);
+ return ERROR;
+ }
+ break;
+ }
+ case DBF_LONG:
+ case DBF_ENUM:
+ {
+ if (streamScanf (record, format, &lval) != OK)
+ {
+ return aao->nord ? OK : ERROR;
+ }
+ switch (aao->ftvl)
+ {
+ case DBF_DOUBLE:
+ ((double *)aao->bptr)[aao->nord] = lval;
+ break;
+ case DBF_FLOAT:
+ ((float *)aao->bptr)[aao->nord] = (float)lval;
+ break;
+ case DBF_LONG:
+ case DBF_ULONG:
+ ((long *)aao->bptr)[aao->nord] = lval;
+ break;
+ case DBF_SHORT:
+ case DBF_USHORT:
+ case DBF_ENUM:
+ ((short *)aao->bptr)[aao->nord] = (short)lval;
+ break;
+ case DBF_CHAR:
+ case DBF_UCHAR:
+ ((char *)aao->bptr)[aao->nord] = (char)lval;
+ break;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "readData %s: can't convert from long to %s\n",
+ record->name, pamapdbfType[aao->ftvl].strvalue);
+ return ERROR;
+ }
+ break;
+ }
+ case DBF_STRING:
+ {
+ switch (aao->ftvl)
+ {
+ case DBF_STRING:
+ if (streamScanfN (record, format,
+ (char *)aao->bptr + aao->nord * MAX_STRING_SIZE,
+ MAX_STRING_SIZE) != OK)
+ {
+ return aao->nord ? OK : ERROR;
+ }
+ break;
+ case DBF_CHAR:
+ case DBF_UCHAR:
+ memset (aao->bptr, 0, aao->nelm);
+ aao->nord = 0;
+ if (streamScanfN (record, format,
+ (char *)aao->bptr, aao->nelm) != OK)
+ {
+ return ERROR;
+ }
+ ((char*)aao->bptr)[aao->nelm] = 0;
+ for (lval = aao->nelm;
+ lval >= 0 && ((char*)aao->bptr)[lval] == 0;
+ lval--);
+ aao->nord = lval+1;
+ return OK;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "readData %s: can't convert from string to %s\n",
+ record->name, pamapdbfType[aao->ftvl].strvalue);
+ return ERROR;
+ }
+ break;
+ }
+ default:
+ {
+ errlogSevPrintf (errlogMajor,
+ "readData %s: can't convert from %s to %s\n",
+ record->name, pamapdbfType[format->type].strvalue,
+ pamapdbfType[aao->ftvl].strvalue);
+ return ERROR;
+ }
+ }
+ }
+ return OK;
+}
+
+static long writeData (dbCommon *record, format_t *format)
+{
+ aaoRecord *aao = (aaoRecord *) record;
+ double dval;
+ long lval;
+ unsigned long nowd;
+
+ for (nowd = 0; nowd < aao->nord; nowd++)
+ {
+ switch (format->type)
+ {
+ case DBF_DOUBLE:
+ {
+ switch (aao->ftvl)
+ {
+ case DBF_DOUBLE:
+ dval = ((double *)aao->bptr)[nowd];
+ break;
+ case DBF_FLOAT:
+ dval = ((float *)aao->bptr)[nowd];
+ break;
+ case DBF_LONG:
+ dval = ((long *)aao->bptr)[nowd];
+ break;
+ case DBF_ULONG:
+ dval = ((unsigned long *)aao->bptr)[nowd];
+ break;
+ case DBF_SHORT:
+ dval = ((short *)aao->bptr)[nowd];
+ break;
+ case DBF_USHORT:
+ case DBF_ENUM:
+ dval = ((unsigned short *)aao->bptr)[nowd];
+ break;
+ case DBF_CHAR:
+ dval = ((char *)aao->bptr)[nowd];
+ break;
+ case DBF_UCHAR:
+ dval = ((unsigned char *)aao->bptr)[nowd];
+ break;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "writeData %s: can't convert from %s to double\n",
+ record->name, pamapdbfType[aao->ftvl].strvalue);
+ return ERROR;
+ }
+ if (streamPrintf (record, format, dval))
+ return ERROR;
+ break;
+ }
+ case DBF_LONG:
+ case DBF_ENUM:
+ {
+ switch (aao->ftvl)
+ {
+ case DBF_LONG:
+ case DBF_ULONG:
+ lval = ((long *)aao->bptr)[nowd];
+ break;
+ case DBF_SHORT:
+ lval = ((short *)aao->bptr)[nowd];
+ break;
+ case DBF_USHORT:
+ case DBF_ENUM:
+ lval = ((unsigned short *)aao->bptr)[nowd];
+ break;
+ case DBF_CHAR:
+ lval = ((char *)aao->bptr)[nowd];
+ break;
+ case DBF_UCHAR:
+ lval = ((unsigned char *)aao->bptr)[nowd];
+ break;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "writeData %s: can't convert from %s to long\n",
+ record->name, pamapdbfType[aao->ftvl].strvalue);
+ return ERROR;
+ }
+ if (streamPrintf (record, format, lval))
+ return ERROR;
+ break;
+ }
+ case DBF_STRING:
+ {
+ switch (aao->ftvl)
+ {
+ case DBF_STRING:
+ if (streamPrintf (record, format,
+ ((char *)aao->bptr) + nowd * MAX_STRING_SIZE))
+ return ERROR;
+ break;
+ case DBF_CHAR:
+ case DBF_UCHAR:
+ /* print aao as a null-terminated string */
+ if (aao->nord < aao->nelm)
+ {
+ ((char *)aao->bptr)[aao->nord] = 0;
+ }
+ else
+ {
+ ((char *)aao->bptr)[aao->nelm-1] = 0;
+ }
+ if (streamPrintf (record, format,
+ ((char *)aao->bptr)))
+ return ERROR;
+ return OK;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "writeData %s: can't convert from %s to string\n",
+ record->name, pamapdbfType[aao->ftvl].strvalue);
+ return ERROR;
+ }
+ break;
+ }
+ default:
+ {
+ errlogSevPrintf (errlogFatal,
+ "writeData %s: can't convert from %s to %s\n",
+ record->name, pamapdbfType[aao->ftvl].strvalue,
+ pamapdbfType[format->type].strvalue);
+ return ERROR;
+ }
+ }
+ }
+ return OK;
+}
+
+static long initRecord (dbCommon *record)
+{
+ static const int typesize[] = {MAX_STRING_SIZE,1,1,2,2,4,4,4,8,2};
+ aaoRecord *aao = (aaoRecord *) record;
+
+ aao->bptr = calloc(aao->nelm, typesize[aao->ftvl]);
+ if (aao->bptr == NULL)
+ {
+ errlogSevPrintf (errlogFatal,
+ "initRecord %s: can't allocate memory for data array\n",
+ record->name);
+ return ERROR;
+ }
+ return streamInitRecord (record, &aao->out, readData, writeData);
+}
+
+struct {
+ long number;
+ DEVSUPFUN report;
+ DEVSUPFUN init;
+ DEVSUPFUN init_record;
+ DEVSUPFUN get_ioint_info;
+ DEVSUPFUN read;
+} devaaoStream = {
+ 5,
+ streamReport,
+ streamInit,
+ initRecord,
+ streamGetIointInfo,
+ streamWrite
+};
+
+epicsExportAddress(dset,devaaoStream);
diff --git a/src/devaiStream.c b/src/devaiStream.c
new file mode 100644
index 0000000..ebfcb31
--- /dev/null
+++ b/src/devaiStream.c
@@ -0,0 +1,98 @@
+/***************************************************************
+* StreamDevice record interface for analog input records *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 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
+#include
+#include
+
+static long readData (dbCommon *record, format_t *format)
+{
+ aiRecord *ai = (aiRecord *) record;
+ double val;
+
+ switch (format->type)
+ {
+ case DBF_DOUBLE:
+ {
+ if (streamScanf (record, format, &val)) return ERROR;
+ if (ai->aslo != 0.0) val *= ai->aslo;
+ val += ai->aoff;
+ if (!INIT_RUN && ai->smoo != 0.0)
+ {
+ val = ai->val * ai->smoo + val * (1.0 - ai->smoo);
+ }
+ ai->val = val;
+ return DO_NOT_CONVERT;
+ }
+ case DBF_LONG:
+ {
+ return streamScanf (record, format, &ai->rval);
+ }
+ }
+ return ERROR;
+}
+
+static long writeData (dbCommon *record, format_t *format)
+{
+ aiRecord *ai = (aiRecord *) record;
+ double val;
+
+ switch (format->type)
+ {
+ case DBF_DOUBLE:
+ {
+ val = ai->val - ai->aoff;
+ if (ai->aslo != 0) val /= ai->aslo;
+ return streamPrintf (record, format, val);
+ }
+ case DBF_LONG:
+ {
+ return streamPrintf (record, format, ai->rval);
+ }
+ }
+ return ERROR;
+}
+
+static long initRecord (dbCommon *record)
+{
+ aiRecord *ai = (aiRecord *) record;
+
+ return streamInitRecord (record, &ai->inp, readData, writeData);
+}
+
+struct {
+ long number;
+ DEVSUPFUN report;
+ DEVSUPFUN init;
+ DEVSUPFUN init_record;
+ DEVSUPFUN get_ioint_info;
+ DEVSUPFUN read;
+ DEVSUPFUN special_linconv;
+} devaiStream = {
+ 6,
+ streamReport,
+ streamInit,
+ initRecord,
+ streamGetIointInfo,
+ streamRead,
+ NULL
+};
+
+epicsExportAddress(dset,devaiStream);
diff --git a/src/devaoStream.c b/src/devaoStream.c
new file mode 100644
index 0000000..54ba9f2
--- /dev/null
+++ b/src/devaoStream.c
@@ -0,0 +1,99 @@
+/***************************************************************
+* Stream Device record interface for analog output records *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 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
+#include
+#include
+
+static long readData (dbCommon *record, format_t *format)
+{
+ aoRecord *ao = (aoRecord *) record;
+
+ switch (format->type)
+ {
+ case DBF_DOUBLE:
+ {
+ double val;
+ if (streamScanf (record, format, &val)) return ERROR;
+ if (ao->aslo != 0) val *= ao->aslo;
+ ao->val = val + ao->aoff;
+ return DO_NOT_CONVERT;
+ }
+ case DBF_LONG:
+ {
+ long rval;
+ if (streamScanf (record, format, &rval)) return ERROR;
+ ao->rbv = rval;
+ if (INIT_RUN) ao->rval = rval;
+ return OK;
+ }
+ }
+ return ERROR;
+}
+
+static long writeData (dbCommon *record, format_t *format)
+{
+ aoRecord *ao = (aoRecord *) record;
+ double val;
+
+ switch (format->type)
+ {
+ case DBF_DOUBLE:
+ {
+ if (INIT_RUN) val = ao->val;
+ else val = ao->oval;
+ val -= ao->aoff;
+ if (ao->aslo != 0) val /= ao->aslo;
+ return streamPrintf (record, format, val);
+ }
+ case DBF_LONG:
+ {
+ return streamPrintf (record, format, ao->rval);
+ }
+ }
+ return ERROR;
+}
+
+static long initRecord (dbCommon *record)
+{
+ aoRecord *ao = (aoRecord *) record;
+
+ return streamInitRecord (record, &ao->out, readData, writeData);
+}
+
+struct {
+ long number;
+ DEVSUPFUN report;
+ DEVSUPFUN init;
+ DEVSUPFUN init_record;
+ DEVSUPFUN get_ioint_info;
+ DEVSUPFUN write;
+ DEVSUPFUN special_linconv;
+} devaoStream = {
+ 6,
+ streamReport,
+ streamInit,
+ initRecord,
+ streamGetIointInfo,
+ streamWrite,
+ NULL
+};
+
+epicsExportAddress(dset,devaoStream);
diff --git a/src/devbiStream.c b/src/devbiStream.c
new file mode 100644
index 0000000..3e7d973
--- /dev/null
+++ b/src/devbiStream.c
@@ -0,0 +1,112 @@
+/***************************************************************
+* Stream Device record interface for binary input records *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 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
+#include
+#include
+#include
+
+static long readData (dbCommon *record, format_t *format)
+{
+ biRecord *bi = (biRecord *) record;
+ unsigned long val;
+
+ switch (format->type)
+ {
+ case DBF_LONG:
+ {
+ if (streamScanf (record, format, &val)) return ERROR;
+ if (bi->mask) val &= bi->mask;
+ bi->rval = val;
+ return OK;
+ }
+ case DBF_ENUM:
+ {
+ if (streamScanf (record, format, &val)) return ERROR;
+ bi->val = (val != 0);
+ return DO_NOT_CONVERT;
+ }
+ case DBF_STRING:
+ {
+ char buffer[sizeof(bi->znam)];
+ if (streamScanfN (record, format, buffer, sizeof(buffer)))
+ return ERROR;
+ if (strcmp (bi->znam, buffer) == 0)
+ {
+ bi->val = 0;
+ return DO_NOT_CONVERT;
+ }
+ if (strcmp (bi->onam, buffer) == 0)
+ {
+ bi->val = 1;
+ return DO_NOT_CONVERT;
+ }
+ }
+ }
+ return ERROR;
+}
+
+static long writeData (dbCommon *record, format_t *format)
+{
+ biRecord *bi = (biRecord *) record;
+
+ switch (format->type)
+ {
+ case DBF_LONG:
+ {
+ return streamPrintf (record, format, bi->rval);
+ }
+ case DBF_ENUM:
+ {
+ return streamPrintf (record, format, (long) bi->val);
+ }
+ case DBF_STRING:
+ {
+ return streamPrintf (record, format,
+ bi->val ? bi->onam : bi->znam);
+ }
+ }
+ return ERROR;
+}
+
+static long initRecord (dbCommon *record)
+{
+ biRecord *bi = (biRecord *) record;
+
+ return streamInitRecord (record, &bi->inp, readData, writeData);
+}
+
+struct {
+ long number;
+ DEVSUPFUN report;
+ DEVSUPFUN init;
+ DEVSUPFUN init_record;
+ DEVSUPFUN get_ioint_info;
+ DEVSUPFUN read;
+} devbiStream = {
+ 5,
+ streamReport,
+ streamInit,
+ initRecord,
+ streamGetIointInfo,
+ streamRead
+};
+
+epicsExportAddress(dset,devbiStream);
diff --git a/src/devboStream.c b/src/devboStream.c
new file mode 100644
index 0000000..3aac446
--- /dev/null
+++ b/src/devboStream.c
@@ -0,0 +1,113 @@
+/***************************************************************
+* Stream Device record interface for binary output records *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 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
+#include
+#include
+#include
+
+static long readData (dbCommon *record, format_t *format)
+{
+ boRecord *bo = (boRecord *) record;
+ unsigned long val;
+
+ switch (format->type)
+ {
+ case DBF_LONG:
+ {
+ if (streamScanf (record, format, &val)) return ERROR;
+ if (bo->mask) val &= bo->mask;
+ bo->rbv = val;
+ if (INIT_RUN) bo->rval = val;
+ return OK;
+ }
+ case DBF_ENUM:
+ {
+ if (streamScanf (record, format, &val)) return ERROR;
+ bo->val = (val != 0);
+ return DO_NOT_CONVERT;
+ }
+ case DBF_STRING:
+ {
+ char buffer[sizeof(bo->znam)];
+ if (streamScanfN (record, format, buffer, sizeof(buffer)))
+ return ERROR;
+ if (strcmp (bo->znam, buffer) == 0)
+ {
+ bo->val = 0;
+ return DO_NOT_CONVERT;
+ }
+ if (strcmp (bo->onam, buffer) == 0)
+ {
+ bo->val = 1;
+ return DO_NOT_CONVERT;
+ }
+ }
+ }
+ return ERROR;
+}
+
+static long writeData (dbCommon *record, format_t *format)
+{
+ boRecord *bo = (boRecord *) record;
+
+ switch (format->type)
+ {
+ case DBF_LONG:
+ {
+ return streamPrintf (record, format, bo->rval);
+ }
+ case DBF_ENUM:
+ {
+ return streamPrintf (record, format, (long) bo->val);
+ }
+ case DBF_STRING:
+ {
+ return streamPrintf (record, format,
+ bo->val ? bo->onam : bo->znam);
+ }
+ }
+ return ERROR;
+}
+
+static long initRecord (dbCommon *record)
+{
+ boRecord *bo = (boRecord *) record;
+
+ return streamInitRecord (record, &bo->out, readData, writeData);
+}
+
+struct {
+ long number;
+ DEVSUPFUN report;
+ DEVSUPFUN init;
+ DEVSUPFUN init_record;
+ DEVSUPFUN get_ioint_info;
+ DEVSUPFUN write;
+} devboStream = {
+ 5,
+ streamReport,
+ streamInit,
+ initRecord,
+ streamGetIointInfo,
+ streamWrite
+};
+
+epicsExportAddress(dset,devboStream);
diff --git a/src/devcalcoutStream.c b/src/devcalcoutStream.c
new file mode 100644
index 0000000..ce698d1
--- /dev/null
+++ b/src/devcalcoutStream.c
@@ -0,0 +1,92 @@
+/***************************************************************
+* Stream Device record interface for calcout records *
+* *
+* (C) 2005 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
+#include
+#include
+#include
+
+static long readData (dbCommon *record, format_t *format)
+{
+ calcoutRecord *co = (calcoutRecord *) record;
+
+ switch (format->type)
+ {
+ case DBF_DOUBLE:
+ {
+ return streamScanf (record, format, &co->val);
+ }
+ case DBF_LONG:
+ case DBF_ENUM:
+ {
+ long lval;
+
+ if (streamScanf (record, format, &lval)) return ERROR;
+ co->val = lval;
+ return OK;
+ }
+ }
+ return ERROR;
+}
+
+static long writeData (dbCommon *record, format_t *format)
+{
+ calcoutRecord *co = (calcoutRecord *) record;
+
+ switch (format->type)
+ {
+ case DBF_DOUBLE:
+ {
+ return streamPrintf (record, format, co->oval);
+ }
+ case DBF_LONG:
+ case DBF_ENUM:
+ {
+ return streamPrintf (record, format, (long)co->oval);
+ }
+ }
+ return ERROR;
+}
+
+static long initRecord (dbCommon *record)
+{
+ calcoutRecord *co = (calcoutRecord *) record;
+
+ return streamInitRecord (record, &co->out, readData, writeData);
+}
+
+struct {
+ long number;
+ DEVSUPFUN report;
+ DEVSUPFUN init;
+ DEVSUPFUN init_record;
+ DEVSUPFUN get_ioint_info;
+ DEVSUPFUN write;
+ DEVSUPFUN special_linconv;
+} devcalcoutStream = {
+ 6,
+ streamReport,
+ streamInit,
+ initRecord,
+ streamGetIointInfo,
+ streamWrite,
+ NULL
+};
+
+epicsExportAddress(dset,devcalcoutStream);
diff --git a/src/devlonginStream.c b/src/devlonginStream.c
new file mode 100644
index 0000000..dac7d86
--- /dev/null
+++ b/src/devlonginStream.c
@@ -0,0 +1,70 @@
+/***************************************************************
+* Stream Device record interface for long input records *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 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
+#include
+#include
+
+static long readData (dbCommon *record, format_t *format)
+{
+ longinRecord *li = (longinRecord *) record;
+
+ if (format->type == DBF_LONG || format->type == DBF_ENUM)
+ {
+ return streamScanf (record, format, &li->val);
+ }
+ return ERROR;
+}
+
+static long writeData (dbCommon *record, format_t *format)
+{
+ longinRecord *li = (longinRecord *) record;
+
+ if (format->type == DBF_LONG || format->type == DBF_ENUM)
+ {
+ return streamPrintf (record, format, li->val);
+ }
+ return ERROR;
+}
+
+static long initRecord (dbCommon *record)
+{
+ longinRecord *li = (longinRecord *) record;
+
+ return streamInitRecord (record, &li->inp, readData, writeData);
+}
+
+struct {
+ long number;
+ DEVSUPFUN report;
+ DEVSUPFUN init;
+ DEVSUPFUN init_record;
+ DEVSUPFUN get_ioint_info;
+ DEVSUPFUN read;
+} devlonginStream = {
+ 5,
+ streamReport,
+ streamInit,
+ initRecord,
+ streamGetIointInfo,
+ streamRead
+};
+
+epicsExportAddress(dset,devlonginStream);
diff --git a/src/devlongoutStream.c b/src/devlongoutStream.c
new file mode 100644
index 0000000..84863a3
--- /dev/null
+++ b/src/devlongoutStream.c
@@ -0,0 +1,71 @@
+/***************************************************************
+* Stream Device record interface for long output records *
+* *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 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
+#include
+#include
+
+static long readData (dbCommon *record, format_t *format)
+{
+ longoutRecord *lo = (longoutRecord *) record;
+
+ if (format->type == DBF_LONG || format->type == DBF_ENUM)
+ {
+ return streamScanf (record, format, &lo->val);
+ }
+ return ERROR;
+}
+
+static long writeData (dbCommon *record, format_t *format)
+{
+ longoutRecord *lo = (longoutRecord *) record;
+
+ if (format->type == DBF_LONG || format->type == DBF_ENUM)
+ {
+ return streamPrintf (record, format, lo->val);
+ }
+ return ERROR;
+}
+
+static long initRecord (dbCommon *record)
+{
+ longoutRecord *lo = (longoutRecord *) record;
+
+ return streamInitRecord (record, &lo->out, readData, writeData);
+}
+
+struct {
+ long number;
+ DEVSUPFUN report;
+ DEVSUPFUN init;
+ DEVSUPFUN init_record;
+ DEVSUPFUN get_ioint_info;
+ DEVSUPFUN write;
+} devlongoutStream = {
+ 5,
+ streamReport,
+ streamInit,
+ initRecord,
+ streamGetIointInfo,
+ streamWrite
+};
+
+epicsExportAddress(dset,devlongoutStream);
diff --git a/src/devmbbiDirectStream.c b/src/devmbbiDirectStream.c
new file mode 100644
index 0000000..084519b
--- /dev/null
+++ b/src/devmbbiDirectStream.c
@@ -0,0 +1,88 @@
+/***************************************************************
+* StreamDevice record interface for *
+* multibit binary input direct records *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 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
+#include
+#include
+
+static long readData (dbCommon *record, format_t *format)
+{
+ mbbiDirectRecord *mbbiD = (mbbiDirectRecord *) record;
+ long val;
+
+ if (format->type == DBF_LONG)
+ {
+ if (streamScanf (record, format, &val)) return ERROR;
+ if (mbbiD->mask)
+ {
+ val &= mbbiD->mask;
+ mbbiD->rval = val;
+ return OK;
+ }
+ else
+ {
+ /* No MASK, (NOBT = 0): use VAL field */
+ mbbiD->val = (short)val;
+ return DO_NOT_CONVERT;
+ }
+ }
+ return ERROR;
+}
+
+static long writeData (dbCommon *record, format_t *format)
+{
+ mbbiDirectRecord *mbbiD = (mbbiDirectRecord *) record;
+ long val;
+
+ if (format->type == DBF_LONG)
+ {
+ if (mbbiD->mask) val = mbbiD->rval & mbbiD->mask;
+ else val = mbbiD->val;
+ return streamPrintf (record, format, val);
+ }
+ return ERROR;
+}
+
+static long initRecord (dbCommon *record)
+{
+ mbbiDirectRecord *mbbiD = (mbbiDirectRecord *) record;
+
+ mbbiD->mask <<= mbbiD->shft;
+ return streamInitRecord (record, &mbbiD->inp, readData, writeData);
+}
+
+struct {
+ long number;
+ DEVSUPFUN report;
+ DEVSUPFUN init;
+ DEVSUPFUN init_record;
+ DEVSUPFUN get_ioint_info;
+ DEVSUPFUN read;
+} devmbbiDirectStream = {
+ 5,
+ streamReport,
+ streamInit,
+ initRecord,
+ streamGetIointInfo,
+ streamRead
+};
+
+epicsExportAddress(dset,devmbbiDirectStream);
diff --git a/src/devmbbiStream.c b/src/devmbbiStream.c
new file mode 100644
index 0000000..b8c0f6c
--- /dev/null
+++ b/src/devmbbiStream.c
@@ -0,0 +1,136 @@
+/***************************************************************
+* StreamDevice record interface for *
+* multibit binary input records *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 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
+#include
+#include
+#include
+
+static long readData (dbCommon *record, format_t *format)
+{
+ mbbiRecord *mbbi = (mbbiRecord *) record;
+ long val;
+ int i;
+
+ switch (format->type)
+ {
+ case DBF_LONG:
+ {
+ if (streamScanf (record, format, &val)) return ERROR;
+ /* read VAL or RBV? Look if any value is defined */
+ if (mbbi->sdef) for (i=0; i<16; i++)
+ {
+ if ((&mbbi->zrvl)[i])
+ {
+ if (mbbi->mask) val &= mbbi->mask;
+ mbbi->rval = val;
+ return OK;
+ }
+ }
+ mbbi->val = (short)val;
+ return DO_NOT_CONVERT;
+ }
+ case DBF_ENUM:
+ {
+ if (streamScanf (record, format, &val)) return ERROR;
+ mbbi->val = (short)val;
+ return DO_NOT_CONVERT;
+ }
+ case DBF_STRING:
+ {
+ char buffer[sizeof(mbbi->zrst)];
+ if (streamScanfN (record, format, buffer, sizeof(buffer)))
+ return ERROR;
+ for (val = 0; val < 16; val++)
+ {
+ if (strcmp ((&mbbi->zrst)[val], buffer) == 0)
+ {
+ mbbi->val = (short)val;
+ return DO_NOT_CONVERT;
+ }
+ }
+ }
+ }
+ return ERROR;
+}
+
+static long writeData (dbCommon *record, format_t *format)
+{
+ mbbiRecord *mbbi = (mbbiRecord *) record;
+ long val;
+ int i;
+
+ switch (format->type)
+ {
+ case DBF_LONG:
+ {
+ /* print VAL or RVAL ? Look if any value is defined */
+ val = mbbi->val;
+ if (mbbi->sdef) for (i=0; i<16; i++)
+ {
+ if ((&mbbi->zrvl)[i])
+ {
+ val = mbbi->rval;
+ if (mbbi->mask) val &= mbbi->mask;
+ break;
+ }
+ }
+ return streamPrintf (record, format, val);
+ }
+ case DBF_ENUM:
+ {
+ return streamPrintf (record, format, (long) mbbi->val);
+ }
+ case DBF_STRING:
+ {
+ if (mbbi->val >= 16) return ERROR;
+ return streamPrintf (record, format,
+ mbbi->zrst + sizeof(mbbi->zrst) * mbbi->val);
+ }
+ }
+ return ERROR;
+}
+
+static long initRecord (dbCommon *record)
+{
+ mbbiRecord *mbbi = (mbbiRecord *) record;
+
+ mbbi->mask <<= mbbi->shft;
+ return streamInitRecord (record, &mbbi->inp, readData, writeData);
+}
+
+struct {
+ long number;
+ DEVSUPFUN report;
+ DEVSUPFUN init;
+ DEVSUPFUN init_record;
+ DEVSUPFUN get_ioint_info;
+ DEVSUPFUN read;
+} devmbbiStream = {
+ 5,
+ streamReport,
+ streamInit,
+ initRecord,
+ streamGetIointInfo,
+ streamRead
+};
+
+epicsExportAddress(dset,devmbbiStream);
diff --git a/src/devmbboDirectStream.c b/src/devmbboDirectStream.c
new file mode 100644
index 0000000..436dfc9
--- /dev/null
+++ b/src/devmbboDirectStream.c
@@ -0,0 +1,89 @@
+/***************************************************************
+* StreamDevice record interface for *
+* multibit binary output direct records *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 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
+#include
+#include
+
+static long readData (dbCommon *record, format_t *format)
+{
+ mbboDirectRecord *mbboD = (mbboDirectRecord *) record;
+ long val;
+
+ if (format->type == DBF_LONG)
+ {
+ if (streamScanf (record, format, &val)) return ERROR;
+ if (mbboD->mask)
+ {
+ val &= mbboD->mask;
+ mbboD->rbv = val;
+ if (INIT_RUN) mbboD->rval = val;
+ return OK;
+ }
+ else
+ {
+ /* No MASK, (NOBT = 0): use VAL field */
+ mbboD->val = (short)val;
+ return DO_NOT_CONVERT;
+ }
+ }
+ return ERROR;
+}
+
+static long writeData (dbCommon *record, format_t *format)
+{
+ mbboDirectRecord *mbboD = (mbboDirectRecord *) record;
+ long val;
+
+ if (format->type == DBF_LONG)
+ {
+ if (mbboD->mask) val = mbboD->rval & mbboD->mask;
+ else val = mbboD->val;
+ return streamPrintf (record, format, val);
+ }
+ return ERROR;
+}
+
+static long initRecord (dbCommon *record)
+{
+ mbboDirectRecord *mbboD = (mbboDirectRecord *) record;
+
+ mbboD->mask <<= mbboD->shft;
+ return streamInitRecord (record, &mbboD->out, readData, writeData);
+}
+
+struct {
+ long number;
+ DEVSUPFUN report;
+ DEVSUPFUN init;
+ DEVSUPFUN init_record;
+ DEVSUPFUN get_ioint_info;
+ DEVSUPFUN write;
+} devmbboDirectStream = {
+ 5,
+ streamReport,
+ streamInit,
+ initRecord,
+ streamGetIointInfo,
+ streamWrite
+};
+
+epicsExportAddress(dset,devmbboDirectStream);
diff --git a/src/devmbboStream.c b/src/devmbboStream.c
new file mode 100644
index 0000000..e7b9e73
--- /dev/null
+++ b/src/devmbboStream.c
@@ -0,0 +1,138 @@
+/***************************************************************
+* StreamDevice record interface for *
+* multibit binary output records *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 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
+#include
+#include
+#include
+
+static long readData (dbCommon *record, format_t *format)
+{
+ mbboRecord *mbbo = (mbboRecord *) record;
+ long val;
+ int i;
+
+ switch (format->type)
+ {
+ case DBF_LONG:
+ {
+ if (streamScanf (record, format, &val)) return ERROR;
+ /* read VAL or RBV? Look if any value is defined */
+ if (mbbo->sdef) for (i=0; i<16; i++)
+ {
+ if ((&mbbo->zrvl)[i])
+ {
+ if (mbbo->mask) val &= mbbo->mask;
+ mbbo->rbv = val;
+ if (INIT_RUN) mbbo->rval = val;
+ return OK;
+ }
+ }
+ mbbo->val = (short)val;
+ return DO_NOT_CONVERT;
+ }
+ case DBF_ENUM:
+ {
+ if (streamScanf (record, format, &val)) return ERROR;
+ mbbo->val = (short)val;
+ return DO_NOT_CONVERT;
+ }
+ case DBF_STRING:
+ {
+ char buffer[sizeof(mbbo->zrst)];
+ if (streamScanfN (record, format, buffer, sizeof(buffer)))
+ return ERROR;
+ for (val = 0; val < 16; val++)
+ {
+ if (strcmp ((&mbbo->zrst)[val], buffer) == 0)
+ {
+ mbbo->val = (short)val;
+ return DO_NOT_CONVERT;
+ }
+ }
+ }
+ }
+ return ERROR;
+}
+
+static long writeData (dbCommon *record, format_t *format)
+{
+ mbboRecord *mbbo = (mbboRecord *) record;
+ long val;
+ int i;
+
+ switch (format->type)
+ {
+ case DBF_LONG:
+ {
+ /* print VAL or RVAL ? */
+ val = mbbo->val;
+ if (mbbo->sdef) for (i=0; i<16; i++)
+ {
+ if ((&mbbo->zrvl)[i])
+ {
+ /* any values defined ? */
+ val = mbbo->rval;
+ if (mbbo->mask) val &= mbbo->mask;
+ break;
+ }
+ }
+ return streamPrintf (record, format, val);
+ }
+ case DBF_ENUM:
+ {
+ return streamPrintf (record, format, (long) mbbo->val);
+ }
+ case DBF_STRING:
+ {
+ if (mbbo->val >= 16) return ERROR;
+ return streamPrintf (record, format,
+ mbbo->zrst + sizeof(mbbo->zrst) * mbbo->val);
+ }
+ }
+ return ERROR;
+}
+
+static long initRecord (dbCommon *record)
+{
+ mbboRecord *mbbo = (mbboRecord *) record;
+
+ mbbo->mask <<= mbbo->shft;
+ return streamInitRecord (record, &mbbo->out, readData, writeData);
+}
+
+struct {
+ long number;
+ DEVSUPFUN report;
+ DEVSUPFUN init;
+ DEVSUPFUN init_record;
+ DEVSUPFUN get_ioint_info;
+ DEVSUPFUN write;
+} devmbboStream = {
+ 5,
+ streamReport,
+ streamInit,
+ initRecord,
+ streamGetIointInfo,
+ streamWrite
+};
+
+epicsExportAddress(dset,devmbboStream);
diff --git a/src/devstringinStream.c b/src/devstringinStream.c
new file mode 100644
index 0000000..0fd6f69
--- /dev/null
+++ b/src/devstringinStream.c
@@ -0,0 +1,70 @@
+/***************************************************************
+* Stream Device record interface for string input records *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 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
+#include
+#include
+
+static long readData (dbCommon *record, format_t *format)
+{
+ stringinRecord *si = (stringinRecord *) record;
+
+ if (format->type == DBF_STRING)
+ {
+ return streamScanfN (record, format, si->val, sizeof(si->val));
+ }
+ return ERROR;
+}
+
+static long writeData (dbCommon *record, format_t *format)
+{
+ stringinRecord *si = (stringinRecord *) record;
+
+ if (format->type == DBF_STRING)
+ {
+ return streamPrintf (record, format, si->val);
+ }
+ return ERROR;
+}
+
+static long initRecord (dbCommon *record)
+{
+ stringinRecord *si = (stringinRecord *) record;
+
+ return streamInitRecord (record, &si->inp, readData, writeData);
+}
+
+struct {
+ long number;
+ DEVSUPFUN report;
+ DEVSUPFUN init;
+ DEVSUPFUN init_record;
+ DEVSUPFUN get_ioint_info;
+ DEVSUPFUN read;
+} devstringinStream = {
+ 5,
+ streamReport,
+ streamInit,
+ initRecord,
+ streamGetIointInfo,
+ streamRead
+};
+
+epicsExportAddress(dset,devstringinStream);
diff --git a/src/devstringoutStream.c b/src/devstringoutStream.c
new file mode 100644
index 0000000..39a72ec
--- /dev/null
+++ b/src/devstringoutStream.c
@@ -0,0 +1,70 @@
+/***************************************************************
+* Stream Device record interface for string output records *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 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
+#include
+#include
+
+static long readData (dbCommon *record, format_t *format)
+{
+ stringoutRecord *so = (stringoutRecord *) record;
+
+ if (format->type == DBF_STRING)
+ {
+ return streamScanfN (record, format, so->val, sizeof(so->val));
+ }
+ return ERROR;
+}
+
+static long writeData (dbCommon *record, format_t *format)
+{
+ stringoutRecord *so = (stringoutRecord *) record;
+
+ if (format->type == DBF_STRING)
+ {
+ return streamPrintf (record, format, so->val);
+ }
+ return ERROR;
+}
+
+static long initRecord (dbCommon *record)
+{
+ stringoutRecord *so = (stringoutRecord *) record;
+
+ return streamInitRecord (record, &so->out, readData, writeData);
+}
+
+struct {
+ long number;
+ DEVSUPFUN report;
+ DEVSUPFUN init;
+ DEVSUPFUN init_record;
+ DEVSUPFUN get_ioint_info;
+ DEVSUPFUN write;
+} devstringoutStream = {
+ 5,
+ streamReport,
+ streamInit,
+ initRecord,
+ streamGetIointInfo,
+ streamWrite
+};
+
+epicsExportAddress(dset,devstringoutStream);
diff --git a/src/devwaveformStream.c b/src/devwaveformStream.c
new file mode 100644
index 0000000..5fe3c89
--- /dev/null
+++ b/src/devwaveformStream.c
@@ -0,0 +1,293 @@
+/***************************************************************
+* StreamDevice record interface for waveform records *
+* *
+* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
+* (C) 2005 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
+#include
+#include
+#include
+
+static long readData (dbCommon *record, format_t *format)
+{
+ waveformRecord *wf = (waveformRecord *) record;
+ double dval;
+ long lval;
+
+ wf->rarm = 0;
+ for (wf->nord = 0; wf->nord < wf->nelm; wf->nord++)
+ {
+ switch (format->type)
+ {
+ case DBF_DOUBLE:
+ {
+ if (streamScanf (record, format, &dval) != OK)
+ {
+ return wf->nord ? OK : ERROR;
+ }
+ switch (wf->ftvl)
+ {
+ case DBF_DOUBLE:
+ ((double *)wf->bptr)[wf->nord] = dval;
+ break;
+ case DBF_FLOAT:
+ ((float *)wf->bptr)[wf->nord] = (float)dval;
+ break;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "readData %s: can't convert from double to %s\n",
+ record->name, pamapdbfType[wf->ftvl].strvalue);
+ return ERROR;
+ }
+ break;
+ }
+ case DBF_LONG:
+ case DBF_ENUM:
+ {
+ if (streamScanf (record, format, &lval) != OK)
+ {
+ return wf->nord ? OK : ERROR;
+ }
+ switch (wf->ftvl)
+ {
+ case DBF_DOUBLE:
+ ((double *)wf->bptr)[wf->nord] = (double)lval;
+ break;
+ case DBF_FLOAT:
+ ((float *)wf->bptr)[wf->nord] = (float)lval;
+ break;
+ case DBF_LONG:
+ case DBF_ULONG:
+ ((long *)wf->bptr)[wf->nord] = lval;
+ break;
+ case DBF_SHORT:
+ case DBF_USHORT:
+ case DBF_ENUM:
+ ((short *)wf->bptr)[wf->nord] = (short)lval;
+ break;
+ case DBF_CHAR:
+ case DBF_UCHAR:
+ ((char *)wf->bptr)[wf->nord] = (char)lval;
+ break;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "readData %s: can't convert from long to %s\n",
+ record->name, pamapdbfType[wf->ftvl].strvalue);
+ return ERROR;
+ }
+ break;
+ }
+ case DBF_STRING:
+ {
+ switch (wf->ftvl)
+ {
+ case DBF_STRING:
+ if (streamScanfN (record, format,
+ (char *)wf->bptr + wf->nord * MAX_STRING_SIZE,
+ MAX_STRING_SIZE) != OK)
+ {
+ return wf->nord ? OK : ERROR;
+ }
+ break;
+ case DBF_CHAR:
+ case DBF_UCHAR:
+ memset (wf->bptr, 0, wf->nelm);
+ wf->nord = 0;
+ if (streamScanfN (record, format,
+ (char *)wf->bptr, wf->nelm) != OK)
+ {
+ return ERROR;
+ }
+ ((char*)wf->bptr)[wf->nelm] = 0;
+ for (lval = wf->nelm;
+ lval >= 0 && ((char*)wf->bptr)[lval] == 0;
+ lval--);
+ wf->nord = lval+1;
+ return OK;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "readData %s: can't convert from string to %s\n",
+ record->name, pamapdbfType[wf->ftvl].strvalue);
+ return ERROR;
+ }
+ break;
+ }
+ default:
+ {
+ errlogSevPrintf (errlogMajor,
+ "readData %s: can't convert from %s to %s\n",
+ record->name, pamapdbfType[format->type].strvalue,
+ pamapdbfType[wf->ftvl].strvalue);
+ return ERROR;
+ }
+ }
+ }
+ return OK;
+}
+
+static long writeData (dbCommon *record, format_t *format)
+{
+ waveformRecord *wf = (waveformRecord *) record;
+ double dval;
+ long lval;
+ unsigned long nowd;
+
+ for (nowd = 0; nowd < wf->nord; nowd++)
+ {
+ switch (format->type)
+ {
+ case DBF_DOUBLE:
+ {
+ switch (wf->ftvl)
+ {
+ case DBF_DOUBLE:
+ dval = ((double *)wf->bptr)[nowd];
+ break;
+ case DBF_FLOAT:
+ dval = ((float *)wf->bptr)[nowd];
+ break;
+ case DBF_LONG:
+ dval = ((long *)wf->bptr)[nowd];
+ break;
+ case DBF_ULONG:
+ dval = ((unsigned long *)wf->bptr)[nowd];
+ break;
+ case DBF_SHORT:
+ dval = ((short *)wf->bptr)[nowd];
+ break;
+ case DBF_USHORT:
+ case DBF_ENUM:
+ dval = ((unsigned short *)wf->bptr)[nowd];
+ break;
+ case DBF_CHAR:
+ dval = ((char *)wf->bptr)[nowd];
+ break;
+ case DBF_UCHAR:
+ dval = ((unsigned char *)wf->bptr)[nowd];
+ break;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "writeData %s: can't convert from %s to double\n",
+ record->name, pamapdbfType[wf->ftvl].strvalue);
+ return ERROR;
+ }
+ if (streamPrintf (record, format, dval))
+ return ERROR;
+ break;
+ }
+ case DBF_LONG:
+ case DBF_ENUM:
+ {
+ switch (wf->ftvl)
+ {
+ case DBF_LONG:
+ case DBF_ULONG:
+ lval = ((long *)wf->bptr)[nowd];
+ break;
+ case DBF_SHORT:
+ lval = ((short *)wf->bptr)[nowd];
+ break;
+ case DBF_USHORT:
+ case DBF_ENUM:
+ lval = ((unsigned short *)wf->bptr)[nowd];
+ break;
+ case DBF_CHAR:
+ lval = ((char *)wf->bptr)[nowd];
+ break;
+ case DBF_UCHAR:
+ lval = ((unsigned char *)wf->bptr)[nowd];
+ break;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "writeData %s: can't convert from %s to long\n",
+ record->name, pamapdbfType[wf->ftvl].strvalue);
+ return ERROR;
+ }
+ if (streamPrintf (record, format, lval))
+ return ERROR;
+ break;
+ }
+ case DBF_STRING:
+ {
+ switch (wf->ftvl)
+ {
+ case DBF_STRING:
+ if (streamPrintf (record, format,
+ ((char *)wf->bptr) + nowd * MAX_STRING_SIZE))
+ return ERROR;
+ break;
+ case DBF_CHAR:
+ case DBF_UCHAR:
+ /* print waveform as a null-terminated string */
+ if (wf->nord < wf->nelm)
+ {
+ ((char *)wf->bptr)[wf->nord] = 0;
+ }
+ else
+ {
+ ((char *)wf->bptr)[wf->nelm-1] = 0;
+ }
+ if (streamPrintf (record, format,
+ ((char *)wf->bptr)))
+ return ERROR;
+ return OK;
+ default:
+ errlogSevPrintf (errlogFatal,
+ "writeData %s: can't convert from %s to string\n",
+ record->name, pamapdbfType[wf->ftvl].strvalue);
+ return ERROR;
+ }
+ break;
+ }
+ default:
+ {
+ errlogSevPrintf (errlogFatal,
+ "writeData %s: can't convert from %s to %s\n",
+ record->name, pamapdbfType[wf->ftvl].strvalue,
+ pamapdbfType[format->type].strvalue);
+ return ERROR;
+ }
+ }
+ }
+ return OK;
+}
+
+static long initRecord (dbCommon *record)
+{
+ waveformRecord *wf = (waveformRecord *) record;
+
+ return streamInitRecord (record, &wf->inp, readData, writeData);
+}
+
+struct {
+ long number;
+ DEVSUPFUN report;
+ DEVSUPFUN init;
+ DEVSUPFUN init_record;
+ DEVSUPFUN get_ioint_info;
+ DEVSUPFUN read;
+} devwaveformStream = {
+ 5,
+ streamReport,
+ streamInit,
+ initRecord,
+ streamGetIointInfo,
+ streamRead
+};
+
+epicsExportAddress(dset,devwaveformStream);
diff --git a/src/memguard.cc b/src/memguard.cc
new file mode 100644
index 0000000..e8008ec
--- /dev/null
+++ b/src/memguard.cc
@@ -0,0 +1,204 @@
+/***************************************************************
+* memguard *
+* *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* Include memguard.h and link memguard.o to get some memory *
+* guardiance. Allocated memory (using new) is tracked with *
+* source file and line of the new operator. Deleted memory is *
+* checked for duplicate deletion. *
+* Call memguardReport() to see all allocated memory. *
+* Calling memguardReport() shows currently allocated memory. *
+* *
+* DISCLAIMER: If this software breaks something or harms *
+* someone, it's your problem. *
+* *
+***************************************************************/
+
+#undef new
+#undef delete
+
+#include
+#include
+#include
+
+struct memheader
+{
+ static memheader* first;
+ memheader* next;
+ size_t size;
+ long line;
+ const char* file;
+ long fill1;
+ long fill2;
+ unsigned long magic;
+};
+
+extern "C" void memguardReport();
+
+class memguard
+{
+public:
+ memguard() {atexit(memguardReport);}
+ void report();
+};
+
+memheader* memheader::first = NULL;
+
+static memguard guard;
+
+extern "C" void memguardReport()
+{
+ guard.report();
+}
+
+void* operator new(size_t size, const char* file, long line) throw (std::bad_alloc)
+{
+ memheader* header;
+ memheader** prev;
+
+ header = (memheader*) malloc(size + sizeof(memheader));
+ if (!header)
+ {
+ fprintf (stderr, "out of memory\n");
+ if (file) fprintf (stderr, "in %s line %ld\n", file, line);
+ guard.report();
+ abort();
+ }
+ header->magic = 0xA110caed;
+ for (prev = &memheader::first; *prev; prev = &(*prev)->next);
+ *prev = header;
+ header->next = NULL;
+ header->size = size;
+ header->line = line;
+ header->file = file;
+// fprintf(stderr, "allocating %p: %d bytes for %s line %ld\n",
+// header+1, size, file, line);
+ return header+1;
+}
+
+
+void* operator new[](size_t size, const char* file, long line) throw (std::bad_alloc)
+{
+ return operator new(size, file, line);
+}
+
+void* operator new(size_t size) throw (std::bad_alloc)
+{
+ return operator new(size, NULL, 0);
+}
+
+void* operator new[](size_t size) throw (std::bad_alloc)
+{
+ return operator new(size, NULL, 0);
+}
+
+static const char* deleteFile = NULL;
+static long deleteLine = 0;
+
+int memguardLocation(const char* file, long line)
+{
+ deleteFile = file;
+ deleteLine = line;
+ return 1;
+}
+
+void operator delete (void* memory) throw ()
+{
+ memheader* header;
+ memheader** prev;
+
+ if (!memory)
+ {
+ deleteFile = NULL;
+ deleteLine = 0;
+ return;
+ }
+ header = ((memheader*)memory)-1;
+ if (header->magic == 0xDeadBeef)
+ {
+ fprintf (stderr, "memory at %p deleted twice\n", memory);
+ if (header->file)
+ fprintf (stderr, "allocated by %s line %ld\n",
+ header->file, header->line);
+ guard.report();
+ abort();
+ }
+ for (prev = &memheader::first; *prev; prev = &(*prev)->next)
+ {
+ if (*prev == header)
+ {
+ if (header->magic != 0xA110caed)
+ {
+ fprintf (stderr, "deleted memory at %p corrupt\n", memory);
+ if (header->file)
+ fprintf (stderr, "allocated by %s line %ld\n",
+ header->file, header->line);
+ guard.report();
+ abort();
+ }
+ *prev = header->next;
+ header->magic = 0xDeadBeef;
+// fprintf(stderr, "deleting %p ", memory);
+// if (header->file) fprintf(stderr, "allocated in %s line %ld\n",
+// header->file, header->line);
+// if (deleteFile) fprintf(stderr, "probably deleted in %s line %ld\n",
+// deleteFile, deleteLine);
+ deleteFile = NULL;
+ deleteLine = 0;
+ free(header);
+// fprintf(stderr, "done\n");
+ return;
+ }
+ }
+ if (deleteFile)
+ {
+ fprintf (stderr, "deleted memory at %p was never allocated\n", memory);
+ fprintf (stderr, "delete was probably called in %s line %ld\n",
+ deleteFile, deleteLine);
+ abort();
+ }
+}
+
+void operator delete[](void* memory) throw ()
+{
+ operator delete(memory);
+}
+
+void memguard::report()
+{
+ memheader* header;
+ unsigned long sum = 0;
+ unsigned long chunks = 0;
+
+ if (memheader::first)
+ {
+ fprintf(stderr, "allocated memory:\n");
+ for (header = memheader::first; header; header = header->next)
+ {
+ chunks++;
+ sum += header->size;
+ if (header->file)
+ {
+ fprintf (stderr, "%p: %d bytes by %s line %ld\n",
+ header+1, header->size, header->file, header->line);
+ }
+ else
+ {
+ fprintf (stderr, "%p: %d bytes by unknown\n",
+ header+1, header->size);
+ }
+ if (header->magic != 0xA110caed)
+ {
+ fprintf (stderr, "memory list is corrupt\n");
+ abort();
+ }
+ }
+ fprintf (stderr, "%lu bytes in %lu chunks\n", sum, chunks);
+ }
+ else
+ {
+ fprintf(stderr, "no memory allocated\n");
+ }
+}
+
diff --git a/src/memguard.h b/src/memguard.h
new file mode 100644
index 0000000..5bd030a
--- /dev/null
+++ b/src/memguard.h
@@ -0,0 +1,30 @@
+/***************************************************************
+* memguard *
+* *
+* (C) 2005 Dirk Zimoch (dirk.zimoch@psi.ch) *
+* *
+* Include memguard.h and link memguard.o to get some memory *
+* guardiance. Allocated memory (using new) is tracked with *
+* source file and line of the new operator. Deleted memory is *
+* checked for duplicate deletion. *
+* Call memguardReport() to see all allocated memory. *
+* Calling memguardReport() shows currently allocated memory. *
+* *
+* DISCLAIMER: If this software breaks something or harms *
+* someone, it's your problem. *
+* *
+***************************************************************/
+
+#ifdef __cplusplus
+#ifndef memguard_h
+#define memguard_h
+#include
+#define MEMGUARD
+void* operator new(size_t, const char*, long) throw (std::bad_alloc);
+void* operator new[](size_t, const char*, long) throw (std::bad_alloc);
+#define new new(__FILE__,__LINE__)
+int memguardLocation(const char*, long);
+#define delete if (memguardLocation(__FILE__,__LINE__)) delete
+extern "C" void memguardReport();
+#endif
+#endif
diff --git a/src/munch.pl b/src/munch.pl
new file mode 100644
index 0000000..02aaade
--- /dev/null
+++ b/src/munch.pl
@@ -0,0 +1,84 @@
+eval 'exec perl -S $0 ${1+"$@"}' # -*- Mode: perl -*-
+ if $running_under_some_shell; # makeConfigAppInclude.pl
+#*************************************************************************
+# Copyright (c) 2002 The University of Chicago, as Operator of Argonne
+# National Laboratory.
+# Copyright (c) 2002 The Regents of the University of California, as
+# Operator of Los Alamos National Laboratory.
+# EPICS BASE Versions 3.13.7
+# and higher are distributed subject to a Software License Agreement found
+# in file LICENSE that is included with this distribution.
+#*************************************************************************
+
+# Creates a ctdt.c file of c++ static constructors and destructors.
+# munch.pl,v 1.10 2002/12/16 20:25:53 jba Exp
+
+@ctorlist = ();
+@dtorlist = ();
+
+while ($line = )
+{
+ chomp;
+ next if ($line =~ /__?GLOBAL_.F.+/);
+ next if ($line =~ /__?GLOBAL_.I._GLOBAL_.D.+/);
+ if ($line =~ /__?GLOBAL_.D.+/) {
+ ($adr,$type,$name) = split ' ',$line,3;
+ $name =~ s/^__/_/;
+ @dtorlist = (@dtorlist,$name);
+ };
+ if ($line =~ /__?GLOBAL_.I.+/) {
+ ($adr,$type,$name) = split ' ',$line,3;
+ $name =~ s/^__/_/;
+ @ctorlist = (@ctorlist,$name);
+ };
+}
+
+foreach $ctor (@ctorlist)
+{
+ if ( $ctor =~ /\./ ) {
+ printf "void %s() __asm__ (\"_%s\");\n",convertName($ctor),$ctor;
+ } else {
+ printf "void %s();\n",$ctor;
+ }
+}
+
+print "extern void (*_ctors[])();\n";
+print "void (*_ctors[])() = {\n";
+foreach $ctor (@ctorlist)
+{
+ if ( $ctor =~ /\./ ) {
+ printf " %s,\n",convertName($ctor);
+ } else {
+ printf " %s,\n",$ctor;
+ }
+}
+print " 0};\n";
+
+
+foreach $dtor (@dtorlist)
+{
+ if ( $dtor =~ /\./ ) {
+ printf "void %s() __asm__ (\"_%s\");\n",convertName($dtor),$dtor;
+ } else {
+ printf "void %s();\n",$dtor;
+ }
+}
+
+print "extern void (*_ctors[])();\n";
+print "void (*_dtors[])() = {\n";
+foreach $dtor (@dtorlist)
+{
+ if ( $dtor =~ /\./ ) {
+ printf " %s,\n",convertName($dtor);
+ } else {
+ printf " %s,\n",$dtor;
+ }
+}
+print " 0};\n";
+
+sub convertName {
+ my ($name) = @_;
+ $name =~ s/\./\$/g;
+ return $name;
+}
+