Files
StreamDevice/doc/tipsandtricks.html
2015-09-15 07:21:41 +00:00

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>
&nbsp;&nbsp;field (DTYP, "stream")<br>
&nbsp;&nbsp;field (INP, "@$(DEVICETYPE).proto read $(BUS)")<br>
&nbsp;&nbsp;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>
&nbsp;&nbsp;field (INPA, "$(A_RECORD)")<br>
&nbsp;&nbsp;field (INPB, "$(B_RECORD)")<br>
&nbsp;&nbsp;field (INPC, "$(C_RECORD)")<br>
&nbsp;&nbsp;field (CALC, "0")<br>
&nbsp;&nbsp;field (DTYP, "stream")<br>
&nbsp;&nbsp;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>
&nbsp;&nbsp;field (DTYP, "stream")<br>
&nbsp;&nbsp;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>
&nbsp;&nbsp;field (DTYP, "stream")<br>
&nbsp;&nbsp;field (INP, "@$(DEVICETYPE).proto read_A $(BUS)")<br>
&nbsp;&nbsp;field (SCAN, "1 second")<br>
}<br>
record (ai, "$(DEVICE):B") {<br>
&nbsp;&nbsp;field (DTYP, "stream")<br>
&nbsp;&nbsp;field (INP, "@$(DEVICETYPE).proto read_B $(BUS)")<br>
&nbsp;&nbsp;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>
&nbsp;&nbsp;field (DTYP, "stream")<br>
&nbsp;&nbsp;field (INP, "@$(DEVICETYPE).proto read_AB($(DEVICE):B) $(BUS)")<br>
&nbsp;&nbsp;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&nbsp;3.24&nbsp;A</code>" or a message like
"<code>device&nbsp;switched&nbsp;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>
&nbsp;&nbsp;field (DTYP, "stream")<br>
&nbsp;&nbsp;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>
&nbsp;&nbsp;field (INPA, "$(DEVICE):readcurrent.SEVR CP")<br>
&nbsp;&nbsp;field (CALC, "A#3")<br>
&nbsp;&nbsp;field (OOPT, "When Non-zero")<br>
&nbsp;&nbsp;field (OUT, "$(DEVICE):clean_2.PROC")<br>
}<br>
record (stringout, "$(DEVICE):clean_2") {<br>
&nbsp;&nbsp;field (VAL, "OK")<br>
&nbsp;&nbsp;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>
&nbsp;&nbsp;extrainput = ignore;<br>
&nbsp;&nbsp;replyTimeout = 1000;<br>
&nbsp;&nbsp;out "GET http://\$1\n\n";<br>
&nbsp;&nbsp;in "%+.1/(?im)&lt;title&gt(.*)&lt\/title&gt;/";<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>&lt;title&gt</code> and
<code>&lt;/title&gt;</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>&lt;TITLE&gt;</code> or
<code>&lt;Title&gt;</code>, so we ask for case insensitive matching with
<code>(?i)</code>.
</p>
<p>
The string should be terminated with the first closing
<code>&lt;/title&gt;</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>
&nbsp;&nbsp;field (DTYP, "stream")<br>
&nbsp;&nbsp;field (INP, "@$(DEVICETYPE).proto get_title($(PAGE)) $(BUS)")<br>
}<br>
record (waveform, "$(DEVICE):longtitle") {<br>
&nbsp;&nbsp;field (DTYP, "stream")<br>
&nbsp;&nbsp;field (INP, "@$(DEVICETYPE).proto get_title($(PAGE)) $(BUS)")<br>
&nbsp;&nbsp;field (FTVL, "CHAR")<br>
&nbsp;&nbsp;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>
&nbsp;&nbsp;extrainput = ignore;<br>
&nbsp;&nbsp;replyTimeout = 1000;<br>
&nbsp;&nbsp;out "GET http://\$1\n\n";<br>
&nbsp;&nbsp;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>