ca:底层通道访问模块
ca模块使用ctypes提供了EPICS通道访问(CA)库的底层包装。epics模块的大部分使用者将不需要关注这里的大部分详细情况,并且将使用简单的过程接口(epics.caget(),epics.caput()等),或者使用epics.PV类创建和使用epics PV对象。
一般描述,与C库的不同
ca模块提供了一个CA库的C接口的相当完整的映射,同时提供了一个愉快的Python经验。预计使用这个模块的任何人某种程度地熟悉通道访问并且知道哪里查询Channel Access Reference Documnent。在这里,我们注意力集中在与C接口地差别,并且认为一般理解这些函数要做什么。
名称改编
作为一般规则,在C库中一个名为ca_XXX的函数在ca模块中将有一个称为XXX的等价函数。这是因为目标是将用以下导入ca模块:
from epics import ca
因而Python函数ca.XXX()将对应这个C函数ca_XXX。即是,CA库调用它的函数ca_XXX,因为C没有命名空间。Python有命名空间,并且因而使用它们。类似的名称un-mangling也发生在常数的DBR前缀,在dbr模块中保存。因而,C常量DBR_STRING变成了在Python中dbr.STRING。
其它变化和省略
在CA库的C版本中的若干函数在Python模块中未被实现。这些未被实现的函数中大部分当前被视为对Python非必须的,虽然这些中一些可以不太麻烦的被添加。更进一步见Omissions。
此外,虽然CA支持若干C中的DBR类型, 但在Python中不是支持它们所有。这里只支持本地类型以及它们的DBR_TIME和DBR_CTRL变体。不支持DBR_STS和DBR_GR变体,由于它们是DBR_CTRL类型的子集,并且空间优化不是你用Python争取的东西。也不支持若干dbr_XXX函数,由于只在能够动态分配内存才需要它们,这在Python中是不必要的。
初始化,终止化以及生命周期
在能够使用通道访问库前,必须初始化它。对于这种需求有三种主要原因:
- 在能够进行任何实际调用前,CA需要指定一个上下文(context)模型(抢占式回调或非抢占式回调)
- ctypes接口需要在使用共享库前装载它
- ctypes也需要
尽可能,ca模块从用户隐藏了CA生命周期的细节,因而显式地初始化一个通道访问会话是不必要地。而是,需要它时立即初始化这个库,并且更改默认设置时才需要干涉。ca模块也处理终止化CA会话,因而CA作为一个程序结束仍然'存活'着,core-dumps和警告消息不会出现。
因为一些用户可能希望自定义初始化和终止化过程,这里将描述详细地步骤。按以下方式处理这些初始化和终止化任务:
- 在ca模块中的libca变量保存一个对CA共享对象库(DLL)的永久,全局索引。
- 调用函数initialize_libca()初始化Libca。这个函数不接受参数,而使用全局布尔PREEMPTIVE_CALLBACK(默认值True)来控制是否使用抢占式回调。
- 使用函数finalize_libca()函数终止libca。通常,注册这个函数,在一个程序用atexit_register()结束时,要被调用。注意:只在体面关闭时才调用这个。如果这个程序崩溃(例如:由于一个非CA相关的原因),这个终止化可能未被做,并且在通道访问服务器上完全关闭对EPICS变量的关闭。
epics.ca.PREEMPTIVE_CALLBACK: 设置是否使用抢占式回调。默认值是True。如果你想要不使用抢占式回调运行,在这个CA库任何其它使用前,必须设置这个变量。启用抢占式回调,EPICS通信将不需要客户端代码持续查询变化。抢占式回调禁用时,你需要频繁用pend_io()和函数pend_event查询epics。
epics.ca.DEFAULT_CONNECTION_TIMEOUT:为connect_channel()设置默认timeout值(秒为单位)。默认值示2.0。
epics.ca.AUTOMONITOR_MAXLENGTH:设置默认数组长度(即是:一个数组有多少个元素),在其之上,自动转成numpy数组并且抑制对PV变量的自动监视。这个默认值是65536。要清楚,具有比这个值更少元素的waveform将被自动监视变化,并且将被转成numpy数组(如果安装了numpy)。更大的waveform将不被自动监视。
更详细见Working with waveform/array data和Strategies for working with large arrays
使用CA模块
很多处理一般通信和线程环境的很多多用途CA函数与C库非常接近:
epics.ca.intialize_libca()
初始化通道访问库。
这装载共享对象库(DLL)来建立通道访问连接。PREEMPTIVE_CALLBACK的值设置抢占式回调模式。
在CA库的任何实际使用前必须调用这个函数,但将自动被withCA()装饰器调用,因此你不需要从大部分真实程序直接调用这个函数。
返回:libca库对象,用于所有后续ca调用。
注意:在任何实际CA调用前,必须调用这个函数。
见withCA装饰器确保初始化CA。
epics.ca.finalize_libca()
关闭通道访问:为在_cache中所有chids运行clear_channel(),接着调用flush_io()和poll()几次。
参数:
maxtime float:等待flush_io()和poll()结束的最长时间(秒为单位)。
epics.ca.pend_io(timeout=1.0)
为I/O查询CA。
epics.ca.pend_event(timeout=1e-5)
为事件查询CA。
epics.ca.poll(evt=1.e-5[,iot=1.0])
一个快捷函数,它等价于:pend_event(evt) pend_io(iot)
epics.ca.create_context()
创建一个新上下文,使用PREEMPTIVE_CALLBACK的值设置上下文类型。注意:支持context_create和create_context(它与CA库其它的Verb_Object更好地一致性)。
参数:ctx int
0:禁用抢占式回调,1:使用抢占式回调,None:使用PREEMPTIVE_CALLBACK的值。
epics.ca.destroy_context()
销毁当前上下文。
epics.ca.current_context()
返回当前上下文。
epics.ca.attach_context(context)
连接到提供的上下文。
epics.ca.detach_context()
分离上下文。
epics.ca.initial_context()
在初始化libca时,连接到创建的上下文。在编写使用CA的线程化程序时,推荐使用这个函数。
epics.ca.client_status(context, level)
向stderr输出有关通道访问的信息,包含对应每个通道的状态,以及搜索和连接的统计数据。
epics.ca.version()
打印通道访问版本字符串,当前,这应该报告'4.13'
epics.ca.message(status)
打印对应通道访问状态返回值的消息。
epics.ca.flush_io()
冲刷I/O
epics.ca.replace_printf_handler(fcn=None)
用提供的函数替换正常的printf()输出句柄(默认为sys.stderr.write())
警告:replace_printf_handler()实际上没有效果。我们认为Python ctypes的实际限制:不支持C va_list函数参量映射为Python。如果你对此感兴趣或者有修复它的想法,请告诉我们。
创建并且连接通道
基本的通道对象是通道ID或chid。用CA库(和ca模块),创建并且操作chid值。这些仅是ctypes.c_long(C long整数),其保存通道C表示的内存地址,但将这些当成对象实例可能是一个好的想法。
epics.ca.create_channel(pvname, connect=False, callback=None, auto_cb=True)
为指定的pvname创建一个通道。
创建一个通道,返回由其它函数使用来识别这个通道的通道ID chid。
参数:
- pvname string:指定PV的名称,为其创建一个通道
- connect bool:是否(尝试)尽快连接PV。
- auto_cb bool:是否自动使用一个内部连接回调。
- callback callable或None:在连接状态发生变化时,要被调用的用户定义的Python函数。
返回:chid ctypes._clong 通道ID
注意:
1) 用户定义的连接回调函数应该准备好接受以下关键字
2) 如果auto_cb是True,使用一个内部的连接回调,因而你不需要显式地连接一个通道,除非你对理解断开的连接有困难。
3) 如果对于PV名称,已经连接了通道,将立即调用这个callback。
epics.ca.connect_channel(chid, timeout=None, verbose=False)
连接一个通道,最长等待一个通道连接timeout秒。它返回连接状态,True或False。通常不需要这个,由于在大多数情况中在需要时将进行隐式连接。
参数:
- chid ctype.c_long:通道ID
- timeout float:等待连接的最长时间。
- verbose bool:是否输出调试信息
返回:connection_state bool:即是,通道是否被连接
注意:
1) 如果timeout是None,使用DEFAULT_CONNECTION_TIMEOUT的值(默认2.0秒)
2) 通常,通道用毫秒连接,并且在首次尝试时连接回调将成功。
3) 对于未连接的通道,将使用'来自_cache的ts'(上次连接尝试的时间戳)和'failure'(失败连接尝试的次数)防止花费大多时间等待可能不再发生的连接。
很多其它函数需要一个有效通道ID,但不是必须一个连接的通道。这些函数实际上与CA库版本一致,并且包括:
epics.ca.name(chid):返回指定通道的PV名。
epics.ca.host_name(chid):返回提供通道的主机名和端口。
epics.ca.element_count(chid):返回在通道的数据中元素数目。对于大多数通道为1,对于waveform通道>1。
epics.ca.replace_access_rights_event(chid, callback=None)
epics.ca.read_access(chid):返回对应一个通道的读访问:1对应True,0对应False
epics.ca.write_access(chid):返回对应一个通道的写访问:1对应True,0对应False
epics.ca.field_type(chid):返回整数DBR字段类型。见来自Table of DBR Types的ftyple列。
epics.ca.clear_channel(chid):清除这个通道。
epics.ca.state(chid):返回对应一个通道的状态(即是:连接状态)。
添加其它Python式的函数:
epics.ca.isConnected(chid):返回是否连接了通道:dbr.CS_CONN==state(chid)。对于一个连接的通道,这是True,对于未连接的通道,是False。
epics.ca.access(chid):返回一个描述读/写访问的字符串:no access, read-only, write-only或read/write之一。
epics.ca.promote_type(chid[,use_time=False[,use_ctrl=False]]):提升一个chid的本地字段类型为它的TIME或CTRL变体。返回对应被提升字段值的整数。
见Table of DBR Types
epics.ca.ca_cache:ca模块保存一个通道的全局缓存,它保存所有已知PVs的连接状态和内部信息。这个缓存不是为了一般用途。
epics.ca.show_cache(print_out=True):输出在当前会话中一个PVs列表到标准输出。使用print_out=False选项,返回这个列表而不是把它输出。
epics.ca.clear_cache():清除EPICS CA连接的全局缓存,并且从CA上下文分离。在做多进程(并且由CAProcess内部做)时,这是重要的,但对完全重置一个通道访问会话是有用的。
与已连接通道的交互
一旦创建并且连接了一个chid,有若干与其通信的方式。这些主要被封装在函数get(), put()和create_subscription(),和其它一些用于获取特定信息的函数。
这些函数是python模块不同于来自底层CA库的大部分函数的地方,并且这几乎由于底层CA函数需要用户提供DBR TYPE和计数以及chid和为数据分配的空间。在python中,不需要这些,并且关键字参量可以被用于指定这些选项。
epics.ca.get(chid,ftype=None,count=None,as_string=False,as_numpy=True,wait=True,timeout=None)
返回对应一个通道的当前值。注意:对于数组数据没有一个单独的格式。
参数:
- chid ctypes.c_long:通道ID
- fytpe int:要使用的字段类型(默认本地类型)
- count int:要返回的最大元素数目(默认返回完整数据)
- as_string bool:是否返回值得字符串表示。
- as_numpy bool:对于数组/waveform数据,是否返回数值得Python表示。
- wait bool:是否等待要被接收的数据,或者立即返回。
- timeout float:在返回前等待数据的最长时间。
返回:data object
通常,这个数据的值。使用wait=False或者数据传递超时,如果通道未被连接,将返回None。
注意:
- 返回None表明一个未完成的get
- as_string选项不如用于PV.get()的as_string完整。对于枚举类型,将返回枚举状态的名称。对于类型char的waveform,将返回字符串表示。对于其它(count>1)的waveforms,将返回一个像<array count=3,type=1>的字符串。
- as_numpy选项将转换waveform数据为一个numpy数组返回。只在导入了numpy,才使用这个。
- wait选项控制是否等待通过网络接收的数据并且实际返回这个值,或者在请求它被发送后立即返回。如果wait=False(即是,立即返回),就说get操作未完成。数据将最终仍然被接收(除非通道断开了),但在内部被存储,并且之后用get_complete()读取。在某些情况下使用wait=False是有用的。
- timeout选项设置在返回None前等待通过网络接收数据的最长时间。这样一个超时时间意味着通道断开,或者数据尺寸比正常大或者网络比正常满。在这种情况下,就说get操作不完整,并且在之后用get_complete(),数据会变得可用。
ftype值的列表见Table of DBR Types。
如何最好地处理非常大型数组的策略的讨论见Strategies for working with large arrays
当使用wait=False可以有一个大的性能提升时的讨论见Strategies for connecting to a large number of PVs。
wait和timeout选项和相关联的get_complete()函数的进一步讨论见The wait and timeout options for get(), ca.get_complete()。
epics.ca.get_with_metadata(chid,ftype=None,count=None,as_string=None,as_string=False,as_numpy=True,wait=True,timeout=None)
返回一个通道的当前值以及元数据。
参数:
- chid ctypes.c_long:通道ID
- ftype int:要使用的字段类型
- count int:返回的最大元素数目(默认返回完整数据)
- as_string bool:是否返回这个值的字符串表示
- as_numpy bool:对于数组/waveform数据是否返回数值Python表示
- wait bool:是否等待要接收的数据,或者立即返回
- timeout:在返回None前,等待数据的最长时间
返回:data dict或None
数据的字典,确保至少有'value'键。取决于ftype,其它键也可以出现:
{'precision', 'units', 'status', 'severity', 'enum_strs', 'status',
'severity', 'timestamp', 'posixseconds', 'nanoseconds',
'upper_disp_limit', 'lower_disp_limit', 'upper_alarm_limit',
'upper_warning_limit', 'lower_warning_limit','lower_alarm_limit',
'upper_ctrl_limit', 'lower_ctrl_limit'}
使用了wait=False,如果通道没有连接或者数据传输超时了,返回None。
其它用法注释见get()。
epics.ca.get_complete(chid,ftype=None,count=None,as_string=False,as_numpy=True,timeout=None)
因为使用了wait=False或者因为数据传递在超时前没有完成,返回一个通道的当前值,结束一个更早地返回None的未完成get()。
参数:
- chid ctypes.c_long:通道ID
- ftype int:要使用的字段类型
- count int:要返回的最大数据数目(默认返回完整数据)
- as_string bool:是否返回这个值的字符串表示。
- as_numpy bool:对应一个数组/waveform数据是否返回数值Python表示。
- timeout float:在返回None前,要等待数据的最长时间
返回:data object
如果先前的get实际上完成了或者这个数据传递也超时了,这个函数将返回None。
注意:
- 默认超时取决于元素数目:default_timeout=1.0+log10(count)(以秒为单位)
- 更多信息参考get()文档。
进一步讨论,见The wait and timeout options for get(), ca.get_complete。
epics.get_complete_with_metadata(chid, ftype=None, count=None, as_string=False, as_numpy=True, timeout=None)
返回通道的当前值和相关联元数据。
因为使用wait=False或者在超时前数据传递未完成,这完成一个更早先返回None的get()。
参数:
- chid ctypes.c_long:通道ID
- ftype int:要使用的字段类型(默认本地类型)
- count int:返回最大元素数目(默认返回完整数据)
- as_string bool:是否返回这个值的字符串表示。
- as_numpy bool:对于数组/waveform数据,是否返回数值Python表示。
- timeout float:在返回None前,等待数据的最长时间。
返回:data dict或None
如果先前的get()实际上完成,或者如果这个数据传递也超时了,这个函数将返回None。
更多用法的注释见get_complete()。
epics.ca.put(chid,value,wait=False,timeout=30,callback=None,callback_data=None,ftype=None)
设置这个通道为一个值,使用选项等待(阻塞)运行到结束,要么当过程结束时,执行一个提供的回调函数。
参数:
- chid ctypes.c_long:通道ID
- wait bool:在返回前,是否等待运行结束。
- timeout float:在返回前,等待运行的最长时间。
- callback None或callable:当运行结束时,运行用户提供的函数
- callback_data object:要传递给一个用户提供的回调函数的额外数据。
- ftype None或int(有效的dbr类型):强制字段类型为一个非本地格式(None将使用本地格式)
返回:status int
1对应成功,超时时-1。
注意:
- 指定一个将重写设置wait=True的回调。
- 将用关键字参量pvname=pvname, data=callback_data调用put-callback函数。
有关put回调的更多见User-supplied Callback functions。
epics.ca.create_subscription(chid, use_time=False, use_ctrl=False, mask=None, callback=None)
创建要更改的上下文。在这个通道发生任何变化时,设置一个要被调用的用户提供的回调函数。
参数:
- chid ctypes.c_long:通道ID
- use_time bool:是否使用对应PV类型的TIME变体。
- use_ctrl bool:是否使用对应PV类型的CTRL变体。
- fytpe 整数或None:要使用的ftype,重写本地类型,use_time或use_ctrl。如果None,查找本地类型,其需要一个连接的通道。
- mask 整数或None:控制哪些变化产生一个回调的位掩码组合(dbr.DBE_ALARM, dbr.DBE_LOG和dbr.DBE_VALUE)。如果None,默认为DEFAULT_SUBSCRIPTION_MASK。
- callback None或callable:在变化时被调用的用户提供的回调函数。
- timeout None或int:用于未连接通道的连接超时时间。
返回:(callback_ref, user_arg_ref, event_id)
返回的元组包含callback_ref和user_arg_ref,它们是引用,应该被维护的与这个订阅存活一样长。(否则,它们可能被垃圾收集,引起没有休止的问题)。event_id是对应这个事件的id(对清除一个订阅有用)。
注意:
在指定名称变量中保存返回的元组。如果返回的参数被垃圾回收了,将发生一个coredump。
如果通道没有连接,必须未一个成功的订阅指定ftype。
更多有关庇阿涅用户提供的回调见User-supplied Callback functions。
警告:
event_id是对应事件的id(对清理一个订阅有用)。你必须在有效变量中保存返回的元组,要么在一个全局变量中要么在一个包含类的数据中。如果你没有保存这个数据,返回的值将被垃圾收集,对这个回调的C级别引用将消息,并且你将见到coredumps。
在linux上,一条消息如:
python: Objects/funcobject.c:451: func_dealloc: Assertion 'g->gc.gc_refs != (-2)' failed.
Abort (core dumped)
是一个对你未保存这个数据的提示。
epics.ca.DEFAULT_SUBSCRIPTION_MASK:在用mask=None调用create_subscription()时,这个值是被使用的默认订阅类型。
初始默认值是dbr.DBE_ALARM|dbr.DBE_VALUE(即是:在警报变化或者值变化超出监视死区时更新)。在位掩码中其它可能的标记是对应存档死区变化的dbr.DBE_LOG。
如果更改了这个值,对于对create_subscription()的所有后续调用,它将更改这个默认,但它将不更改任何已有的订阅。
epics.ca.clear_subscription(event_id):取消指定它的event_id的订阅。
提供的若干其它函数:
epics.ca.get_timestamp(chid):返回一个通道的时间戳--上次更新的事件。
epics.ca.get_severity(chid):返回一个通道的严重性。
epics.ca.precision(chid):返回一个通道的精度。对于除了FLOAT和DOUBLE的本地类型,这将是0。
epics.ca.get_enum_strings(chid):返回对应一个通道的ENUM枚举的名称的列表。对于非ENUM通道返回None。
epics.ca.get_ctrlvars(chid):返回对应一个通道的CTRL字段。
取决于本地类型,键可能包括:
status, severity, precision, units, enum_strs*, upper_disp_limit, lower_disp_limit, upper_alarm_limit*, lower_alarm_limit, upper_warning_limit*, lower_warning_limit, upper_ctrl_limit, lower_ctrl_limit
注意:enum_strs将是ENUM状态的名称的字符串的列表。
见Table of Control Attributes。
控制属性的表格
注意:enum_strs将是一个ENUM状态的名称的字符串的元组。
epics.ca.get_timevars(chid):返回对应一个通道的TIME字段的字典。这将包含status, severity和timestamp的键。
同步组
警告:
在pyepics中仿真同步组,但不推荐,并且在pyepics内使用可能没有意义,并且还是使用异步I/O。
同步组可以用于确保一个通道访问调用的集合都一起发生,就像在一个事务。在pyepics中应该避免同步组,并且未经过良好测试。它们可能在异步I/O上下文没有意义。出于历史原因这里给出文档。
想法是首先创建一个同步组,接着添加一系列不立即发生的sg_put()和sg_get(),并且为这个组作为一个单元进行所有的通道访问通信时阻塞。在一个同步组的构建过程中,不发出pend_io()是重要的,由于这将引起即将发生的sg_put()和sg_get()执行。
epics.ca.sg_create():创建同步组。返回一个组id,gid,其用于标识这个组并且用于传递给所有其它同步组命令。
epics.ca.sg_delete(gid):删除一个同步组。
epics.ca.sg_block(gid[,timeout=1.0]):为一个同步组阻塞来完成运行。
epics.ca.sg_get(gid,chid[,ftype=None[,as_string=False[,as_numpy=True]]]):对应一个通道的当前值的同步组get。选项与get()相同。这个函数将不立即返回值,而是底层数据的地址。在sg_block()结束之后,你必须使用_unpack()转换这个数据地址为实际值。
示例:
>>> chid = epics.ca.create_channel(PV_Name)
>>> epics.ca.connect_channel(chid1)
>>> sg = epics.ca.sg_create()
>>> data = epics.ca.sg_get(sg, chid)
>>> epics.ca.sg_block(sg)
>>> print epics.ca._unpack(data, chid=chid)
epics.ca.sg_put(gid, chid, value):在一个同步组内执行一个put。
epics.ca.sg_test(gid):测试一个同步组是否完成。
epics.ca.sg_reset(gid):重置一个同步组。
未实现的细节
这里给出的细节对于那些阅读ca模块实现的人,对内部感兴趣的人或者那些想要转换更底层C或Python代码到这个模块的人:
DBR数据类型
Table of DBR类型
CA type | integer ftype | Python ctypes type |
---|---|---|
string | 0 | string |
int | 1 | integer |
short | 1 | integer |
float | 2 | double |
enum | 3 | integer |
char | 4 | byte |
long | 5 | integer |
double | 6 | double |
time_string | 14 | |
time_int | 15 | |
time_short | 15 | |
time_float | 16 | |
time_enum | 17 | |
time_char | 18 | |
time_long | 19 | |
time_double | 20 | |
ctrl_string | 28 | |
ctrl_int | 29 | |
ctrl_short | 29 | |
ctrl_float | 30 | |
ctrl_enum | 31 | |
ctrl_char | 32 | |
ctrl_long | 33 | |
ctrl_double | 34 |
PySEVCHK和ChannelAccessException:检查CA返回代码
exception epics.ca.ChannelAccessException
当ca模块经历想不到的行为是并且必须引发异常时,产生这个异常。
epics.ca.PySEVCHK(func_name, status[,expected=dbr.ECA_NORMAL])
这检查从一个libca.ca_***返回的状态并且如果这个值不匹配预计的值(其通常是dbr.ECA_NORMAL)时,引起一个ChannelAccessException。
来自这个异常的消息将包含func_name(Python函数的名称)和来自message()的CA消息。
epics.ca.withSEVCHK(fcn)
如果包装的ca函数不返回状态=dbr.ECA_NORMAL,要引起ChannelAccessException的装饰器。这处理常见情况:为一个其返回值来自于一个对应的libca函数并且其返回值应该应该是dbr.ECA_NORMAL的函数运行PySEVCHK。
函数装饰器
除了withSEVCHK()外,在ca.py内部大量使用若干装饰器函数,或为你提供便利。
epics.ca.withCA(fcn):在通道访问库的函数调用前,装饰器确保libca和一个上下文被创建。这是为了需要启动CA工作的函数,诸如create_channel()。
注意:接收一个通道ID(chid)作为参数的CA函数未被这个包裹:要获取一个chid,库必须已经被初始化了。
epics.ca.withCHID(fcn):确保一个函数的第一个参数是一个完全连接的通道ID chid的装饰器。执行的测试非常弱,由于任何ctypes long或python int将传递,但在大部分偶尔错误产生CA库崩溃前,捕获它们足够有用。
epics.ca.withConnectedCHID(fcn):确保一个函数的第一个参数是一个完全连接的通道ID的装饰器。这个测试是为了健壮性,并且将尝试在调用这个装饰函数前,确认一个chid实际上被连接。
epics.ca.withIntialContext(fcn):确保被包装函数使用在CA初始化时创建的初始线程上下文的装饰器。
进一步讨论见Using Python Thread。
从Callbacks解包数据
贯穿这个实现,有若干由底层CA库返回数据的地方,需要被转换成Python数据。这被封装在_unpack()函数中。一般,你将不必运行此代码,但有一个例外:当使用sg_get()时,必须用这个函数解包返回的值。
epics.ca._unpack(chid,data[,count=None[,ftype=None[,as_numpy=None]]])
解包对应一个通道ID chid的原始数据,这些数据由包括ca_array_get_callback的函数或者订阅回调的libca函数返回,并且返回对应的Python数据。
通常,预计用户不需要访问这个函数,但在使用sg_get()时将是必需的。
参数:
- chid ctypes.c_long或None:通道ID(如果不为None,用于确定count和ftype)
- data object:由内部libca函数返回的原始数据
- count integer:要获取的元素数目(默认为chid的元素数目或者1)
- ftype integer:通道的数据类型(默认chid的本地类型)
- as_numpy bool:是否转换成numpy数组。
用户提供的回调函数
可以为put(), replace_access_rights_event()和create_subscription提供用户提供的回调函数。注意:用于PV对象的回调稍微不同:详细见pv模块中User-supplied Callback functions。
当定义一个一个回调函数,要么在一个put()结束时或通道发生变化时,如从create_subscription()设置,要么在来自replace_access_rights_event()的读/写权限变化时,被运行,知道两件事是重要的:
- 将如何调用你的函数
- 在你的回调函数内部做什么是允许的。
将用用于put()和用于create_subscription()的关键字参量调用回调。你应该准备传递它们给你的函数。使用**kw,除非你非常确定什么将被发送。对于replace_acces_rights_event()的情况,将传递位置参数。
对在一个put()结束时, 发送的回调,你的函数将被传递这些:
- pvname:pv的名称
- data:用户提供的callback_data(默认为None)
对于订阅回调,将用包含以下的键/值对调用你的函数:
- pvname:pv的名称
- value:最新值
- count:数据元素数目
- ftype:表明数据类型的数值CA类型
- status:PV的状态(0对应OK)
- chid:对应通道ID的整数地址
对于访问权限事件回调,你的函数将被传递:
- read_access:表明读访问状态的布尔值
- write_access:表明写访问状态的布尔值
取决于数据类型,以及是否使用CTRL或TIME变体,回调函数也可能包含这些中的一些作为关键字参量:
- enum_strs:枚举字符串的列表
- precision:十进制精度位数
- units:对应PV单位的字符串
- severity:PV严重性
- timestamp:来自CA服务器的时间戳
- posixseconds:来自CA服务器的POSIX纪元时间戳的整数秒
- nanoseconds:来自CA服务器的时间戳整数纳秒。
注意:一个用户提供的回调将在CA函数中运行,并且不能可靠地进行任何其它CA调用。认为"这在都一个pend_event()调用中发生"并且在一个可能是或者可能不是你程序主线程的epics线程中是有帮助地。建议保持回调函数简短并且非资源密集。考虑只对一个变化已经发生的记录并在之后响应那个变化(可能在一个单独线程中,可能在pend_event()结束之后)使用回调的策略。
省略
在Python模块中没有实现CA库的若干部分。这些当前被视作不需要,但能够按需求添加它们。
epics.ca.ca_add_exception_event():未实现。在合适地方,引起Python异常,并且可以在用户代码中使用。
epics.ca.ca_add_fd_registration():未实现。
epics.ca.ca_client_status():未实现。
epics.ca.ca_set_puser():未实现。根据需要,传递用户定义的数据给回调。
epics.ca.ca_puser():未实现。根据需要,传递用户定义的数据给回调。
epics.ca.ca_SEVCHK():未实现。Python函数PySEVCHK()是几乎相同。
epics.ca.ca_signal():未实现。Python函数PySEVCHK()是几乎相同。
epics.ca.ca_test_event():未实现。这是一个用于调式事件的函数。通过直接调用Python回调函数,这些仿真非常容易。
epics.ca.ca_dump_dbr():未实现。
仅支持本地类型和它们的DBR_TIME和DBR_CTRL变体:DBR_STS和DBR_GR变体不被支持。也不支持若干dbr_XXX函数,由于只在动态分配内存才需要它们。
CAThread类
class epics.ca.CAThread(group=None[,target=None[name=None[,args=0[,kwargs={}]]]])
创建一个标准Python threading.Thread的CA感知的子类。有关如何使用Thread对象的进一步信息键标准库文档。一个CAThread只是在运行每个对象函数前运行了use_initial_context(),因而use_initial_context()不是必须显式地放在在目标函数内。
进一步讨论见Using Python Threads。
示例
这是一些使用ca模块地示例会话。
通道的创建,连接,获取值
注意:相较于在C中的CA,若干事情已经被简化了:初始化和创建一个主线程上下文被处理了,在后台处理了通道连接。
from epics import ca
chid = ca.create_channel('XXX:m1.VAL')
count = ca.element_count(chid)
ftype = ca.field_type(chid)
print "Channel ", chid, count, ftype
value = ca.get()
print value
put,等待完成
在这里,我们设置一个PVs值,等待它结束:
from epics import ca
chid = ca.create_channel('XXX:m1.VAL')
ca.put(chid, 1.0, wait=True)
put()方法将等待到运行结束才返回。
定义一个订阅变化的回调
在这里,我们订阅了一个PV的变化,就说,我们定义了一个回调函数,在这个PV值变化时被调用。在以下情况中,要被调用的函数只是把最新值输出到了标准输出:
from epics import ca
import time
import sys
# define a callback function. Note that this should
# expect certain keyword arguments, including 'pvname' and 'value'
def onChanges(pvname=None, value=None, **kw):
fmt = 'New Value: %s value=%s, kw=%s\n'
sys.stdout.write(fmt % (pvname, str(value), repr(kw)))
sys.stdout.flush()
# create the channel
mypv = 'XXX.VAL'
chid = ca.create_channel(mypv)
# subscribe to events giving a callback function
eventID = ca.create_subscription(chid, callback=onChanges)
# now we simply wait for changes
t0 = time.time()
while time.time()-t0 < 10.0:
time.sleep(0.001)
在一个变量中保存从create_subscription()的返回值使其不能被垃圾回收是关键的 。保存这个值失败将引起麻烦,包含几本立即段错误(在windows上)或者之后可能令人费解的崩溃。
定义一个连接回调
我们在这里定义一个连接回调--在PV连接状态变化时,调用这个函数。注意:在初始连接时将调用这个:
import epics
import time
def onConnectionChange(pvname=None, conn=None, chid=None):
print 'ca connection status changed: ', pvname, conn, chid
# create channel, provide connection callback
motor1 = '13IDC:m1'
chid = epics.ca.create_channel(motor1, callback=onConnectionChange)
print 'Now waiting, watching values and connection changes:'
t0 = time.time()
while time.time()-t0 < 30:
time.sleep(0.001)
在创建这个通道之后,当进行了一次成功连接时,这将很快运行提供的回调。注意:回调应该准备接收对应这个PV名称,通道ID以及连接状态(True/False)的pvname,chid和conn的关键字参量。
定义一个访问权变化事件回调
以下示例演示了在一个通道的访问权变化时一个要被调用的其它函数。注意:在成功安装后立即调用这个:
import epics
import time
def on_access_rights_change(read_access, write_access):
print 'read access = %s, write access = %s' % (read_access, write_access)
# create a channel and attach the above function for access rights events
chid = epics.ca.create_channel('pv_name')
# a message should be printed immediately following this registration
epics.ca.replace_access_rights_event(chid, callback=on_access_rights_change)
# Affecting the channel's access rights, (for example, by enabling/disabling
# CA Security rules), should produce additional console messages
try:
while True:
time.sleep(0.25)
except KeyboardInterrupt:
pass