StreamDevice Tips & Tricks
StreamDeviceのオンラインドキュメントにあるtips & tricksは非常に有用なんですがいまいちわかり辛いので、多少解かりやすくなるように私なりに書き直してみようと思います。
(別のページにも少し例が記載されています)
たくさんの同じようなプロトコルがある
INP,OUT fieldには引数を使うことができるので、このように書くことも可能。
field (OUT, "@protocolfile protocol(arg1,arg2,arg3) bus")
プロトコルファイル内では 引数に数値(バイナリ)を設定したい場合には$1 $2 $3、文字列を設定する場合には"\$1 \$2 \$3"と記述する。
例1(文字列を設定)
- db file
reocrd(ao, "$(RECORD):ex01") { field (OUT, "@motor.proto moveaxis(X) motor1") field (DTYP, "stream") }
- protocol file
moveaxis { out "move\$1 %.6f"; }
この例では、\$1が文字列Xで置き換わり
moveaxis { out "moveX %.6f"; }
と、同じ意味になる。
例2(数値を設定)
- db file
reocrd(ao, "$(RECORD):ex02") { field (INP, "@vacuumgauge.proto readpressure(0x84) gauge3") field (DTYP, "stream") }
- protocol file
readpressure { out 0x02 0x00 $1; in 0x82 0x00 $1 "%2r"; }
この例では、引数 0x84 が $1 に設定されるので、
readpressure { out 0x02 0x00 0x84; in 0x82 0x00 0x84 "%2r"; }
となる。
デバイスが要らないデータをつけて送ってくる
I/O intr処理で送られてきた文字列と同じ場合だけ処理する。(別にI/O intr処理だけではないが例として)
- db file
record (ai, "$(RECORD):ex03") { field (DTYP, "stream") field (INP, "@$(DEVICETYPE).proto read $(BUS)") field (SCAN, "I/O Intr") }
- protocol file
read { in "new value = %f"; }
この場合、空白も含めてまったく同じ文字列じゃないと処理されないので注意。
複数行のデータを送ってくる
例として、こんな文字列を送ってくるデバイスだとする。
Here is the value: 3.1415
その場合、inを複数記述すればいい。
- db file
record (ai, "$(RECORD):ex04") { field (DTYP, "stream") field (INP, "@$(DEVICETYPE).proto read_value $(BUS)") field (SCAN, "I/O Intr") }
- protocol file
read_value { in "Here is the value:"; in "%f"; }
一つのメッセージで複数のデータを出力する必要がある
この問題には色々なアプローチで、複数の解決策がある。
全部同じ型で、同じ文字で区切られているデータを出力
waveform,aaoレコードを使って処理が可能。
- db file
record(aao, "$(RECORD):ex05") { field(OUT, "@$(DEVICETYPE).proto array_out $(BUS)") field(DTYP, "stream") field(FTVL, "DOUBLE") }
- protocol file
array_out { separator=", "; out "an array: (%.2f)"; }
%.2fのフォーマット文字列が配列数分、", "で連結されて、こんな文字列が作成される。
an array: (1.23, 2.34, 3.45, 4.56)
最大12個のデータを出力
calcout のINPA,INPB,...に直接設定。
%(A),%(B),...がINPA,INPB,...に設定される。
CALC fieldには計算をしなくても何かしらの設定は必須。
- db file
record (calcout, "$(RECORD):ex06") { 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)") }
- protocol file
write_ABC { out "A=%(A).2f B=%(B).6f C=%(C).0f"; }
同じIOCの違うレコードの値を出力
プロトコルファイルの引数$1には文字列が設定されるので、レコード名(その一部)も設定可能
- db file
record (stringout, "$(DEVICE):getimage") { field (DTYP, "stream") field (OUT, "@$(DEVICETYPE).proto acquire($(DEVICE)) $(BUS)") }
- protocol file
acquire { out 'ACQUIRE "%(\$1:directory)s/%s",%(\$1:time).3f;'; }
この例では、$(DEVICE)が引数として設定されているが、$(DEVICE)が"TEST"とすると、プロトコルファイル内では次のように展開される。
acquire { out 'ACQUIRE "%(TEST:directory)s/%s",%(TEST:time).3f;'; }
%()にはそのレコードのVALが展開され、出力文字列が生成される。TEST:directoryが"./TEST"、TEST:timeが123.456とすると
acquire { out 'ACQUIRE "./TEST/%s",123.456;'; }
ということになる。
一つのメッセージで複数のデータを取得する必要がある
この問題にも色々なアプローチで、複数の解決策がある。
全部同じ型で、同じ文字で区切られているデータを入力
waveform,aaiレコードを使って処理が可能。
%fのフォーマット文字列が","で連結されているこんな文字列を処理する。
array = (1.23, 2.34, 3.45, 4.56)
- db file
record(waveform, "$(RECORD):ex08") { field(INP, "@$(DEVICETYPE).proto array_in $(BUS)") field(DTYP, "stream") field(FTVL, "DOUBLE") }
- protocol file
array_in { separator=","; in "array = (%f)"; }
メッセージと値を分離して取得
1つのメッセージを文字列と数値に分離して所得。
- db file
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") }
- protocol file
read_A { out "GET A,B"; in "A=%f, B=%*f"; } read_B { in "A=%*f, B=%f"; }
まず、レコード $(DEVICE):Aがデータを受信し、read_AのA=%fの部分のみをフィルタリングして処理する。 B=%*fの*はデータを無視するためのフラグでこのレコードではBをレコード値としては扱わないが、構文チェックは普通に行われるので、適当な文字列を設定するとエラーになるので注意。
次に、レコード $(DEVICE):BがI/O Intrでイベントを取得し、B=%fの部分を処理する。
この処理で重要なのは、レコード $(DEVICE):BのSCANがI/O Intrであることと、read_Bがread_Aのinと同じフォーマットで*でフィルタリングを行っていること。
入力データは、同じIOCの他のレコードに設定
プロトコルファイルの引数にレコードを設定して、出力をリダイレクトさせる。引数を複数設定することも可能。
- db file
record (ai, "$(DEVICE):A") { field (DTYP, "stream") field (INP, "@$(DEVICETYPE).proto read_AB($(DEVICE):B) $(BUS)") field (SCAN, "1 second") } record (ai, "$(DEVICE):B") { }
- protpcol file
read_AB { out "GET A,B"; in "A=%f, B=%(\$1)f"; }
この場合、$(DEVICE):Bのレコード名が長すぎるたり、引数が多すぎるとエラーになる。これは、INP フィールドの文字列長制限によるもの。
数字と文字列の混合データを処理する必要がある
@mismatch例外ハンドラとレコードのリダイレクトで、プロトコルファイルの引数に設定したレコードにエラーメッセージを設定。
例
"CURRENT?"を出力すると、正常な場合には"CURRENT 3.24 A"、異常な場合には"device switched off"と応答するデバイス
- db file
record (ai, "$(DEVICE):readcurrent") { field (DTYP, "stream") field (INP, "@$(DEVICETYPE).proto read_current($(DEVICE):message) $(BUS)") } record (stringin, "$(DEVICE):message") { }
- protocol file
read_current { out "CURRENT?"; in "CURRENT %f A"; @mismatch { in "%(\$1)39c"; } }
in に設定した文字列と違うデータが来た場合、@mismatchの中の処理が実行される。ただし、ここでもエラーになった場合には処理は中断されて、コンソールにエラーが出力される。
Webページのデータを取得する必要がある
まず、正しくフォーマットされたHTMLのリクエストを送信する必要がある。"http://サーバー/ページ"と2行改行で終了する必要があり、この要求のような完全なURLが含まれている必要があることに注意。サーバーは、drvAsynIPPortConfigure(HTTPプロキシを使用しない場合)コマンドを使用する。ウェブページは、多くの場合、かなりのデータ量があるので必要な部分のみ 正規表現で取得するようにしたほうがいい。
- db file
record (stringin, "$(DEVICE):title") { field (DTYP, "stream") field (INP, "@$(DEVICETYPE).proto get_title($(PAGE)) $(BUS)") } record (waveform, "$(DEVICE):longtitle") { field (DTYP, "stream") field (INP, "@$(DEVICETYPE).proto get_title($(PAGE)) $(BUS)") field (FTVL, "CHAR") field (NELM, "100") }
- protocol file
get_title { extrainput = ignore; replyTimeout = 1000; out "GET http://\$1\n\n"; in "%+.1/(?im)<title>(.*)<\/title>/"; }
outに"\n\n"を設定するか、outTerminator="\n\n"を設定する必要がある。接続先のURLはプロトコルファイルの引数として渡すのが妥当。