= !StreamDevice Tips & Tricks = [http://epics.web.psi.ch/software/streamdevice/doc/ StreamDeviceのオンラインドキュメント]にある[http://epics.web.psi.ch/software/streamdevice/doc/tipsandtricks.html 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,...に直接設定。[[br]] %(A),%(B),...がINPA,INPB,...に設定される。[[br]] 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}}}をレコード値としては扱わないが、構文チェックは普通に行われるので、適当な文字列を設定するとエラーになるので注意。[[br]] 次に、{{{レコード $(DEVICE):B}}}が{{{I/O Intr}}}でイベントを取得し、{{{B=%f}}}の部分を処理する。[[br]] この処理で重要なのは、'''{{{レコード $(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}}}の中の処理が実行される。ただし、ここでもエラーになった場合には処理は中断されて、コンソールにエラーが出力される。[[br]] エラー出力が全く必要なければ、{{{extrainput=ignore}}}を設定してもいい。