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.
# 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"
}
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
;.
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.
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.
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;in string;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;event(eventcode) milliseconds;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;disconnect;in or out command will automatically
  reconnect.
  Only records reading in 
  "I/O Intr" mode
  will not cause a reconnect.
 connect milliseconds;milliseconds
  timeout.
  Since connection is handled automatically, this command is normally not
  needed.
  It may be useful after a disconnect.
 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 input matches any byte, in output it does not print
anything.
\_ in input matches any amount of white space (including none),
in output it prints a single space.
\$ 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 (not case sesitive),
or -0200 to 0377, respectively. 
StreamDevice also recognizes the ASCII symbolic names
(not case sensitive) for several byte codes:
NUL (= 0x00) null
SOH (= 0x01) start of heading
STX (= 0x02) start of text
ETX (= 0x03) end of text
EOT (= 0x04) end of transmission
ENQ (= 0x05) enquiry
ACK (= 0x06) acknowledge
BEL (= 0x07) bell
BS  (= 0x08) backspace
HT or TAB (= 0x09) horizontal tabulator
LF or NL (= 0x0A or 10) line feed / new line
VT  (= 0x0B or 11) vertical tabulator
FF or NP (= 0x0C or 12) form feed / new page
CR  (= 0x0D or 13) carriage return
SO  (= 0x0E or 14) shift out
SI  (= 0x0F or 15) shift in
DLE (= 0x10 or 16) data link escape
DC1 (= 0x11 or 17) device control 1
DC2 (= 0x12 or 18) device control 2
DC3 (= 0x13 or 19) device control 3
DC4 (= 0x14 or 20) device control 4
NAK (= 0x15 or 21) negative acknowledge
SYN (= 0x16 or 22) synchronous idle
ETB (= 0x17 or 23) end of transmission block
CAN (= 0x18 or 24) cancel
EM  (= 0x19 or 25) end of medium
SUB (= 0x1A or 26) substitute
ESC (= 0x1B or 27) escape
FS  (= 0x1C or 28) file separator
GS  (= 0x1D or 29) group separator
RS  (= 0x1E or 30) record separator
US  (= 0x1F or 31) unit separator
DEL (= 0x7F or 127) delete
SKIP or ? 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.
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
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.
This is a list of system variables, their default settings and what they influence.
LockTimeout = 5000;out command in a protocol.WriteTimeout = 100;out commands.ReplyTimeout = 1000;in commands.LockTimeout should be larger than
  ReplyTimeout.
 ReadTimeout = 100;in commands.InTerminator = "", a read timeout is not an error
  but a valid input termination.
 PollPeriod = $ReplyTimeout;in command in
  I/O Intr mode (see chapter
  Record Processing).in command at
  the moment.
  How many milliseconds to wait after last poll or last received
  input before polling again?
  A good value is about half the time of the expected input period.
  Longer values cause latency and shorter values may increase CPU
  consumption.
  If not set the same value as for ReplyTimeout is
  used.
 Terminatorout and in commands.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;out commands.InTerminator = $Terminator;in commands.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;in commands.0 means "infinite".
 Separator = "";out and in commands.Separator is a
  space, it matches any number of any whitespace characters in
  an in command."\_".
  
 ExtraInput = Error;Error or Ignore.
  Affects in commands.ExtraInput = Ignore;
 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 same protocol can be used in the OUT link
of three different records as move(X),
move(Y) and move(Z).
Up to 9 parameters can be specified in parentheses, separated by comma.
In the protocol, they are referenced as $1 ...
$9 outside quotes or \$1 ... \$9
within quotes. The parameter $0 resolves to the protocol name.
To make links more readable, one space is allowed before and after each comma and the enclosing parentheses. This space is not part of the parameter string. Any additional space is part of the parameter.
If a parameter contains matching pairs of parentheses, these and all commas
inside are part of the parameter.
This allows to pass parameter strings like (1,2) easily without
much escaping.
Unmatched parentheses must be escaped with double backslash \\
as well as must be commas outside pairs of parentheses.
Double backslash is necessary because one backslash is already consumed by
the db file parser.
To pass a literal backslash in a parameter string use 4 backslashes
\\\\.
Note that macros can be used in parameters. That makes it possible to pass part of the record name to the protocol to be used in redirections.
record(ai, "$(PREFIX)recX5") {
    field(DTYP, "stream")
    field(INP,  "@$(PROTOCOLFILE) read(5, X\\,Y $(PREFIX)) $(PORT)")
}
record(ai, "$(PREFIX)recY5") {}
read { out 0x8$1 "READ \$2"; in "%f,%(\$3recY\$1)f" }
The protocol resolves to:
read { out 0x85 "READ X,Y"; in "%f,%($(PREFIX)recY5)f" }
Here $(PREFIX) is replaced with its macro value.
But be aware that the macro is actually replaced before the link is parsed so
that macro values containing comma or parentheses may have unintended effects.
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"; 
}
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
@.
@mismatchin command.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).
 @writetimeoutout command.out commands in the handler are
  also likely to fail in this case.
 @replytimeoutin command.in commands in the handler are
  also likely to fail in this case.
 @readtimeoutin command.@init
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.