EPICS-Python interfaceについて。
山本昇
(Rev.0.3, revised on Feb. 21, 1998)
Python 言語からEPICS CAをコールするためのライブラリの原形を作成した。また、このライブラリを使い簡単なCAのインターフェイスを作成した。

1.jpg







現状でのパフォーマンス、等
メリット:
ユーザのカスタマイズの範囲が広い。Pythonはインタープリタ言語ではあるが、近代的なプログラム言語の機能を豊富にそろえている。

  1. Pythonの実行環境は、Unix,Windows95/NT,Macと幅が広い。(MacではいまのところEPICSにはアクセスできない。)
  2. Python言語はJavaなどに比べ素早い開発に向いている。実行時の速度はjavaの方が期待できる。
  3. Python 言語はTclなどの言語にくらべ、人間が読むときの読みやすさに配慮されている。
  4. Pythonのオブジェクト指向機能を用いることで、device等のコントロール画面をObjec化し再利用することが見通しよく行なえる。
現状での問題点
  1. MEDM、strip tool などに比べると早い変化には対応出来ない。
  2. エラー処理等が現状では不十分
ソフトウェアの構成
ライブラリはC言語で書かれた_ca.cとPythonで記述されたchannel クラス定義ファイルca.pyから構成される。
ca_test.pyは channel クラスとTkのWidgetを用いたCA コンポーネントの例である。

ca.py はpython 上でEPICS CAを利用する際の直接のインターフェイス(API)を定義している。基本的には、caクラスのインスタンスをチャンネル名を基に作成し、

import ca
chan=ca.channel("channelname.VAL")
chan.wait_conn()

後は、get/putで値を変更することができる。get/putではca_searchがまだ終了していない場合には、pend_ioをコールして、接続のチャンスをまつ。接続に失敗するとPythonのエラーをraiseする。Pythonのtry-exceptを使ってこのエラーを検出することが出来る。

try:
    chan.get();chan.pend_event(0.1)
    print chan.val
except:
    print "Connection ERROR!"

ただし、各get/putの後で、chan.valを見てもこの値とEPICS Runtime データベース上の値は必ずしも一致しない。pend_event() を明示的に呼ぶことで、chan.valの値がデータベース上の値と一致することが保証される。(pend_event()の引数はタイムアウトの時間である。これが短い場合には、chan.valが更新されない場合がる。この場合には再度pend_event()を実行してやる。)

ca.channelのmonitorはcallbackルーチンを定義する。channelの値に変更があれば、callbackに定義した関数が実行される。EPICSのcallbackはpend_io()やpend_event()などのルーチンが呼ばれたときに実行される。CaPythonをTkinter環境下で利用する際には、Tkinterのevent loopの中で、処理されるのでユーザがそれを意識する必要はそれほどない。それ以外の場合には注意が必要である。

ca.pyではPythonのオブジェクト指向機能を使って、ca classを定義してある。実際のchannelアクセスはCで記述されたPythonモジュールの関数によって行われる。_ca.cがPythonの関数とEPICS CAライブラリを結ぶWrapper 関数である。

Python関数名とCの関数名はstatic PyMethodDef 構造体の CA_Methods 中で定義されている。これらの関数は、Py_ParseTupleによる引き数の解析、C-関数のCall、PyBuildObjectによる戻り値の定義が必要である。
 

[参考文献]
Python Tutorial (英文)(日本語訳)
Python Library Reference(英文)
NoodleTimerの作り方
Python ノート
Python-oracle interface

付録:本文で使われたプログラムのソースコード
# FILE: ca_test.py
# import EPICS -CA and Tk libraries
import ca
from Tkinter import *

class Simple(ca.channel,Frame):

def __init__(self,name,master=None,*cnf):
Frame.__init__(self,master)
self.pack(expand=1,fill='both')
ca.channel.__init__(self,name);self.pendio(0.1)
self.get();self.pendio()
l=Label(self,text="CA readback")
l.pack()
s=Scale(self,orient="horizontal",label=name)
s.pack(expand=1,fill='x')
s.set(self.val)
b=Button(self,text="Quit",command=self.quit,text=`self.val`)
b.pack()
self.s=s
self.b=b
self.l=l
self.monitor(self.update_scale)
self.monitor(self.update_label)
self.pendio()
self.s.config(command=self.scale_change)
 
def scale_change(self,arg):
v=self.s.get()
if( v <> self.val):
self.put(v)
self.pend_event(0.0005)


def update_label(self,val):

self.val,self.stat=val
self.l.config(text=`self.val`)


def update_scale(self,val):

self.val,self.stat=val
self.s.set(self.val)


# execute the following lines if loaded as main program.
if(__name__ == "__main__"):

Simple("FFTB:WS2",master=Tk())
Simple("FFTB:WS3",master=Toplevel())
Simple("FFTB:WS1",master=Toplevel()).mainloop()