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.
Terminator
out
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
@
.
@mismatch
in
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).
@writetimeout
out
command.out
commands in the handler are
also likely to fail in this case.
@replytimeout
in
command.in
commands in the handler are
also likely to fail in this case.
@readtimeout
in
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.