413 lines
12 KiB
HTML
413 lines
12 KiB
HTML
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
|
"http://www.w3.org/TR/html4/loose.dtd">
|
|
<html>
|
|
<head>
|
|
<title>StreamDevice: Tips and Tricks</title>
|
|
<link rel="shortcut icon" href="favicon.ico">
|
|
<link rel="stylesheet" type="text/css" href="stream.css">
|
|
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
|
<meta name="author" content="Dirk Zimoch">
|
|
</head>
|
|
<body>
|
|
<iframe src="nav.html" id="navleft"></iframe>
|
|
<h1>Tips and Tricks</h1>
|
|
|
|
<a name="argvar"></a>
|
|
<h2>I have many almost identical protocols</h2>
|
|
<p>
|
|
You can give <a href="protocol.html#argvar">arguments</a> to a protocol.
|
|
In the <code>INP</code> or <code>OUT</code> link, write:
|
|
</p>
|
|
<p>
|
|
<code>
|
|
field (OUT, "@protocolfile protocol(arg1,arg2,arg3) bus")
|
|
</code>
|
|
</p>
|
|
<p>
|
|
In the protocol, reference arguments as <code>$1 $2 $3</code> or inside strings
|
|
as <code>"\$1 \$2 \$3"</code>.
|
|
</p>
|
|
<p>
|
|
<code>
|
|
moveaxis {out "move\$1 %.6f";}<br>
|
|
field (OUT, "@motor.proto moveaxis(X) motor1")
|
|
</code>
|
|
</p>
|
|
<p>
|
|
<code>
|
|
readpressure {out 0x02 0x00 $1; in 0x82 0x00 $1 "%2r";}<br>
|
|
field (INP, "@vacuumgauge.proto readpressure(0x84) gauge3")
|
|
</code>
|
|
</p>
|
|
|
|
<a name="iointr"></a>
|
|
<h2>I have a device that sends unsolicited data</h2>
|
|
<p>
|
|
Use <a href="processing.html#iointr"><code>I/O Intr</code> processing</a>.
|
|
The record receives any input and processes only when the input matches.
|
|
</p>
|
|
<p>
|
|
<code>
|
|
read {in "new value = %f";}
|
|
</code>
|
|
</p>
|
|
<p>
|
|
<code>
|
|
record (ai, "$(RECORD)") {<br>
|
|
field (DTYP, "stream")<br>
|
|
field (INP, "@$(DEVICETYPE).proto read $(BUS)")<br>
|
|
field (SCAN, "I/O Intr")<br>
|
|
}
|
|
</code>
|
|
</p>
|
|
|
|
<a name="multiline"></a>
|
|
<h2>I have a device that sends multi-line messages</h2>
|
|
<pre>
|
|
Here is the value:
|
|
3.1415
|
|
</pre>
|
|
<p>
|
|
Use as many <code>in</code> commands as you get input lines.
|
|
</p>
|
|
<p>
|
|
<code>
|
|
read_value {in "Here is the value:"; in "%f";}
|
|
</code>
|
|
</p>
|
|
|
|
<a name="writemany"></a>
|
|
<h2>I need to write more than one value in one message</h2>
|
|
<p>
|
|
There is more than one solution to this problem.
|
|
Different approaches have different requirements.
|
|
</p>
|
|
<h3>A) All values have the same type and are separated by the same string</h3>
|
|
<p>
|
|
Use array records (e.g. <a href="waveform.html">waveform</a>,
|
|
<a href="aao.html">aao</a>).
|
|
</p>
|
|
<p>
|
|
<code>
|
|
array_out {separator=", "; out "an array: (%.2f)";}
|
|
</code>
|
|
</p>
|
|
<p>
|
|
The format <code>%.2f</code> is repeated for each element of the array.
|
|
All elements are separated by <code>", "</code>.<br>
|
|
Output will look like this:
|
|
</p>
|
|
<pre>
|
|
an array: (3.14, 17.30, -12.34)
|
|
</pre>
|
|
<h3>B) We have up to 12 numeric values</h3>
|
|
<p>
|
|
Use a <a href="calcout.html">calcout</a> record and
|
|
<a href="formats.html#redirection">redirection to fields</a>.
|
|
</p>
|
|
<p>
|
|
<code>
|
|
write_ABC {out "A=%(A).2f B=%(B).6f C=%(C).0f";}
|
|
</code>
|
|
</p>
|
|
<p>
|
|
You must specify a valid expression in the <code>CALC</code> field
|
|
even if you don't use it.
|
|
</p>
|
|
<p>
|
|
<code>
|
|
record (calcout, "$(RECORD)") {<br>
|
|
field (INPA, "$(A_RECORD)")<br>
|
|
field (INPB, "$(B_RECORD)")<br>
|
|
field (INPC, "$(C_RECORD)")<br>
|
|
field (CALC, "0")<br>
|
|
field (DTYP, "stream")<br>
|
|
field (OUT, "@$(DEVICETYPE).proto write_ABC $(BUS)")<br>
|
|
}
|
|
</code>
|
|
</p>
|
|
<h3>C) Values are in other records on the same IOC</h3>
|
|
<p>
|
|
Use <a href="formats.html#redirection">redirection to records</a>.
|
|
</p>
|
|
<p>
|
|
<code>
|
|
acquire {out 'ACQUIRE "%(\$1:directory)s/%s",%(\$1:time).3f;';}
|
|
</code>
|
|
</p>
|
|
<p>
|
|
You can specify a record name or record.FIELD in parentheses directly
|
|
after the <code>%</code>.
|
|
To avoid plain record names in protocol files use
|
|
<a href="protocol.html#argvar">protocol arguments</a> like <code>\$1</code>.
|
|
In the link, specify the record name or just the basename of the
|
|
other records (device name) in parentheses.
|
|
</p>
|
|
<p>
|
|
<code>
|
|
record (stringout, "$(DEVICE):getimage") {<br>
|
|
field (DTYP, "stream")<br>
|
|
field (OUT, "@$(DEVICETYPE).proto acquire($(DEVICE)) $(BUS)")<br>
|
|
}
|
|
</code>
|
|
</p>
|
|
|
|
<a name="readmany"></a>
|
|
<h2>I need to read more than one value from one message</h2>
|
|
<p>
|
|
Again, there is more than one solution to this problem.
|
|
</p>
|
|
<h3>A) All values have the same type and are separated by the same string</h3>
|
|
<p>
|
|
Use array records (e.g. <a href="waveform.html">waveform</a>,
|
|
<a href="aai.html">aai</a>).<br>
|
|
</p>
|
|
<p>
|
|
<code>
|
|
array_in {separator=","; in "array = (%f)";}
|
|
</code>
|
|
</p>
|
|
<p>
|
|
The format <code>%f</code> is repeated for each element of the array.
|
|
A <code>","</code> is expected beween element.<br>
|
|
Input may look like this:
|
|
</p>
|
|
<pre>
|
|
array = (3.14, 17.30, -12.34)
|
|
</pre>
|
|
|
|
<h3>B) The message and the values in it can be filtered easily</h3>
|
|
<p>
|
|
Use <a href="processing.html#iointr"><code>I/O Intr</code> processing</a>
|
|
and <a href="formats.html#syntax">value skipping</a> (<code>%*</code>)<br>
|
|
</p>
|
|
<p>
|
|
<code>
|
|
read_A {out "GET A,B"; in "A=%f, B=%*f";}<br>
|
|
read_B {in "A=%*f, B=%f";}
|
|
</code>
|
|
</p>
|
|
<p>
|
|
<code>
|
|
record (ai, "$(DEVICE):A") {<br>
|
|
field (DTYP, "stream")<br>
|
|
field (INP, "@$(DEVICETYPE).proto read_A $(BUS)")<br>
|
|
field (SCAN, "1 second")<br>
|
|
}<br>
|
|
record (ai, "$(DEVICE):B") {<br>
|
|
field (DTYP, "stream")<br>
|
|
field (INP, "@$(DEVICETYPE).proto read_B $(BUS)")<br>
|
|
field (SCAN, "I/O Intr")<br>
|
|
}
|
|
</code>
|
|
</p>
|
|
<p>
|
|
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 <code>*</code> flag.
|
|
Nevertheless, a complete syntax check is performed: B must be a
|
|
valid floating point number.
|
|
Record B is <code>I/O Intr</code> 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.
|
|
</p>
|
|
<h3>C) Values should be stored in other records on the same IOC</h3>
|
|
<p>
|
|
Use <a href="formats.html#redirection">redirection to records</a>.
|
|
To avoid record names in protocol files, use
|
|
<a href="protocol.html#argvar">protocol arguments</a>.
|
|
</p>
|
|
<p>
|
|
<code>
|
|
read_AB {out "GET A,B"; in "A=%f, B=%(\$1)f";}
|
|
</code>
|
|
</p>
|
|
<p>
|
|
<code>
|
|
record (ai, "$(DEVICE):A") {<br>
|
|
field (DTYP, "stream")<br>
|
|
field (INP, "@$(DEVICETYPE).proto read_AB($(DEVICE):B) $(BUS)")<br>
|
|
field (SCAN, "1 second")<br>
|
|
}<br>
|
|
record (ai, "$(DEVICE):B") {<br>
|
|
}
|
|
</code>
|
|
</p>
|
|
<p>
|
|
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.
|
|
</p>
|
|
|
|
<a name="mixed"></a>
|
|
<h2>I have a device that sends mixed data types: numbers or strings</h2>
|
|
<p>
|
|
Use a <code>@mismatch</code>
|
|
<a href="protocol.html#except">exception handler</a> and
|
|
<a href="formats.html#redirection">redirection to records</a>.
|
|
To avoid record names in protocol files, use
|
|
<a href="protocol.html#argvar">protocol arguments</a>.
|
|
</p>
|
|
<h3>Example</h3>
|
|
<p>
|
|
When asked "<code>CURRENT?</code>", the device send something like
|
|
"<code>CURRENT 3.24 A</code>" or a message like
|
|
"<code>device switched off</code>".
|
|
</p>
|
|
<p>
|
|
<code>
|
|
read_current {out "CURRENT?"; in "CURRENT %f A"; @mismatch {in "%(\1)39c";}}
|
|
</code>
|
|
</p>
|
|
<p>
|
|
<code>
|
|
record (ai, "$(DEVICE):readcurrent") {<br>
|
|
field (DTYP, "stream")<br>
|
|
field (INP, "@$(DEVICETYPE).proto read_current($(DEVICE):message) $(BUS)")<br>
|
|
}<br>
|
|
record (stringin, "$(DEVICE):message") {<br>
|
|
}
|
|
</code>
|
|
</p>
|
|
<p>
|
|
After <a href="processing.html#proc">processing</a> 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.
|
|
</p>
|
|
<code>
|
|
record (calcout, "$(DEVICE):clean_1") {<br>
|
|
field (INPA, "$(DEVICE):readcurrent.SEVR CP")<br>
|
|
field (CALC, "A#3")<br>
|
|
field (OOPT, "When Non-zero")<br>
|
|
field (OUT, "$(DEVICE):clean_2.PROC")<br>
|
|
}<br>
|
|
record (stringout, "$(DEVICE):clean_2") {<br>
|
|
field (VAL, "OK")<br>
|
|
field (OUT, "$(DEVICE):message PP")<br>
|
|
}<br>
|
|
</code>
|
|
<a name="web"></a>
|
|
<h2>I need to read a web page</h2>
|
|
<p>
|
|
First you have to send a correctly formatted HTML request.
|
|
Note that this request must contain the full URL like
|
|
"http://server/page" and must be terminated with <u>two</u> newlines.
|
|
The server should be the same as in the
|
|
<a href="setup.html#sta"><code>drvAsynIPPortConfigure</code></a>
|
|
command (if not using a http proxy).
|
|
|
|
The web page you get often contains much more information than you need.
|
|
<a href="formats.html#regex">Regular expressions</a> are great
|
|
to find what you are looking for.
|
|
</p>
|
|
<h3>Example 1</h3>
|
|
<p>
|
|
Read the title of a web page.
|
|
</p>
|
|
<p>
|
|
<code>
|
|
get_title {<br>
|
|
extrainput = ignore;<br>
|
|
replyTimeout = 1000;<br>
|
|
out "GET http://\$1\n\n";<br>
|
|
in "%+.1/(?im)<title>(.*)<\/title>/";<br>
|
|
}
|
|
</code>
|
|
</p>
|
|
<p>
|
|
Terminate the request with two newlines, either explicit like here
|
|
<u>or</u> using an
|
|
<a href="protocol.html#sysvar"><code>outTerminator</code></a>.
|
|
The URI (without http:// but including the web server host name)
|
|
is passed as <a href="protocol.html#argvar">argument</a> 1 to <code>\$1</code>.
|
|
Note that web servers may be slow, so allow some
|
|
<a href="protocol.html#argvar"><code>replyTimeout</code></a>.
|
|
</p>
|
|
<p>
|
|
If you don't use an <code>inTerminator</code> then the whole page is
|
|
read as one "line" to the <code>in</code> command and can be parsed easily
|
|
with a regular expression.
|
|
We want to see the string between <code><title></code> and
|
|
<code></title></code>, so we put it into a subexpression in
|
|
<code>()</code> and request the first subexpression with <code>.1</code>.
|
|
Note that the <code>/</code> in the closing tag has be be escaped
|
|
to avoid a misinterpretation as the closing <code>/</code> of the regular
|
|
expression.
|
|
</p>
|
|
<p>
|
|
The tags may be upper or lower case like <code><TITLE></code> or
|
|
<code><Title></code>, so we ask for case insensitive matching with
|
|
<code>(?i)</code>.
|
|
</p>
|
|
<p>
|
|
The string should be terminated with the first closing
|
|
<code></title></code>, not the last one in the file.
|
|
(There should not be more than one title but you never know.)
|
|
Thus we ask not to be greedy with <code>(?m)</code>.
|
|
<code>(?i)</code> and <code>(?m)</code> can be combined to <code>(?im)</code>.
|
|
See the PCRE documentation for more regexp syntax.
|
|
</p>
|
|
<p>
|
|
The regular expression matcher ignores and discards any content before the
|
|
matching section.
|
|
Content after the match is discarded with <code>extrainput = ignore</code>
|
|
so that it does not trigger errors reporting "surplus input".
|
|
</p>
|
|
<p>
|
|
Finally, the title may be too long for the record.
|
|
The <code>+</code> tells the format matcher not to fail in this case
|
|
but to truncate the string instead.
|
|
You can read the string with a stringin record or for longer strings with
|
|
a waveform record with data type CHAR.
|
|
</p>
|
|
<p>
|
|
<code>
|
|
record (stringin, "$(DEVICE):title") {<br>
|
|
field (DTYP, "stream")<br>
|
|
field (INP, "@$(DEVICETYPE).proto get_title($(PAGE)) $(BUS)")<br>
|
|
}<br>
|
|
record (waveform, "$(DEVICE):longtitle") {<br>
|
|
field (DTYP, "stream")<br>
|
|
field (INP, "@$(DEVICETYPE).proto get_title($(PAGE)) $(BUS)")<br>
|
|
field (FTVL, "CHAR")<br>
|
|
field (NELM, "100")<br>
|
|
}<br>
|
|
</code>
|
|
</p>
|
|
|
|
<h3>Example 2</h3>
|
|
<p>
|
|
Read a number from a web page. First we have to locate the number.
|
|
For that we match against any known string right before the number
|
|
(and <a href="formats.html#syntax">discard the match</a> with <code>*</code>).
|
|
Then we read the number.
|
|
</p>
|
|
<code>
|
|
get_title {<br>
|
|
extrainput = ignore;<br>
|
|
replyTimeout = 1000;<br>
|
|
out "GET http://\$1\n\n";<br>
|
|
in "%*/Interesting value:/%f more text";<br>
|
|
}
|
|
</code>
|
|
<p>
|
|
When using <code>extrainput = ignore;</code>, it is always a good idea to
|
|
match a few bytes after the value, too.
|
|
This catches errors where loading of the page is interrupted in the middle
|
|
of the number. (You don't want to miss the exponent from something like 1.23E-14).
|
|
</p>
|
|
<p>
|
|
You can read more than one value from a file with successive regular expressions
|
|
and <a href="formats.html#redirection">redirections</a>.
|
|
But this only works if the order of the values is predictible.
|
|
<i>StreamDevice</i> is not an XML parser! It always reads sequentially.
|
|
</p>
|
|
<hr>
|
|
<p><small>Dirk Zimoch, 2012</small></p>
|
|
</body>
|
|
</html>
|