This commit is contained in:
@@ -0,0 +1,988 @@
|
||||
"""
|
||||
CaChannel class having identical API as of caPython/CaChannel class,
|
||||
based on PythonCA ( > 1.20.1beta2)
|
||||
|
||||
Author: Xiaoqiang Wang
|
||||
Created: Sep. 22, 2008
|
||||
Changes:
|
||||
"""
|
||||
# python 2 -> 3 compatible layer
|
||||
import sys
|
||||
if sys.hexversion >= 0x03000000:
|
||||
long = int
|
||||
|
||||
import ca
|
||||
|
||||
ca.cs_never_search = 4
|
||||
|
||||
# retrieve numeric waveforms as numpy arrays, default No
|
||||
USE_NUMPY = False
|
||||
|
||||
class CaChannelException(Exception):
|
||||
def __init__(self, status):
|
||||
self.status = str(status)
|
||||
def __str__(self):
|
||||
return self.status
|
||||
|
||||
class CaChannel:
|
||||
"""CaChannel: A Python class with identical API as of caPython/CaChannel.
|
||||
|
||||
This class implements the methods to operate on channel access so that you can find
|
||||
their C library counterparts ,
|
||||
http://www.aps.anl.gov/epics/base/R3-14/12-docs/CAref.html#Function.
|
||||
Therefore an understanding of C API helps much.
|
||||
|
||||
To get started easily, convenient methods are created for often used operations,
|
||||
|
||||
========== ======
|
||||
Operation Method
|
||||
========== ======
|
||||
connect :meth:`searchw`
|
||||
read :meth:`getw`
|
||||
write :meth:`putw`
|
||||
========== ======
|
||||
|
||||
They have shorter names and default arguments. It is recommended to start with these methods.
|
||||
Study the other C alike methods when necessary.
|
||||
|
||||
>>> import CaChannel
|
||||
>>> chan = CaChannel.CaChannel('catest')
|
||||
>>> chan.searchw()
|
||||
>>> chan.putw(12.3)
|
||||
>>> chan.getw()
|
||||
12.3
|
||||
"""
|
||||
|
||||
ca_timeout = 3.0
|
||||
|
||||
dbr_d = {}
|
||||
dbr_d[ca.DBR_SHORT] = int
|
||||
dbr_d[ca.DBR_INT] = int
|
||||
dbr_d[ca.DBR_LONG] = int
|
||||
dbr_d[ca.DBR_FLOAT] = float
|
||||
dbr_d[ca.DBR_DOUBLE]= float
|
||||
dbr_d[ca.DBR_CHAR] = int
|
||||
dbr_d[ca.DBR_STRING]= str
|
||||
dbr_d[ca.DBR_ENUM] = int
|
||||
|
||||
def __init__(self, pvName=None):
|
||||
self.pvname = pvName
|
||||
self.__chid = None
|
||||
self.__evid = None
|
||||
self.__timeout = None
|
||||
self._field_type = None
|
||||
self._element_count = None
|
||||
self._puser = None
|
||||
self._conn_state = None
|
||||
self._host_name = None
|
||||
self._raccess = None
|
||||
self._waccess = None
|
||||
|
||||
self._callbacks={}
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self.clear_event()
|
||||
self.clear_channel()
|
||||
self.flush_io()
|
||||
except:
|
||||
pass
|
||||
|
||||
def version(self):
|
||||
return "CaChannel, version v28-03-12"
|
||||
#
|
||||
# Class helper methods
|
||||
#
|
||||
def setTimeout(self, timeout):
|
||||
"""Set the timeout for this channel object. It overrides the class timeout.
|
||||
|
||||
:param float timeout: timeout in seconds
|
||||
|
||||
"""
|
||||
if (timeout>=0 or timeout is None):
|
||||
self.__timeout = timeout
|
||||
else:
|
||||
raise ValueError
|
||||
def getTimeout(self):
|
||||
"""Retrieve the timeout set for this channel object.
|
||||
|
||||
:return: timeout in seconds for this channel instance
|
||||
|
||||
"""
|
||||
if self.__timeout is None:
|
||||
timeout = CaChannel.ca_timeout
|
||||
else:
|
||||
timeout = self.__timeout
|
||||
|
||||
return timeout
|
||||
|
||||
|
||||
#
|
||||
# *************** Channel access medthod ***************
|
||||
#
|
||||
|
||||
#
|
||||
# Connection methods
|
||||
# search_and_connect
|
||||
# search
|
||||
# clear_channel
|
||||
|
||||
def search_and_connect(self, pvName, callback, *user_args):
|
||||
"""Attempt to establish a connection to a process variable.
|
||||
|
||||
:param str pvName: process variable name
|
||||
:param callable callback: function called when connection completes and connection status changes later on.
|
||||
:param user_args: user provided arguments that are passed to callback when it is invoked.
|
||||
:raises CaChannelException: if error happens
|
||||
|
||||
The user arguments are returned to the user in a tuple in the callback function.
|
||||
The order of the arguments is preserved.
|
||||
|
||||
Each Python callback function is required to have two arguments.
|
||||
The first argument is a tuple containing the results of the action.
|
||||
The second argument is a tuple containing any user arguments specified by ``user_args``.
|
||||
If no arguments were specified then the tuple is empty.
|
||||
|
||||
|
||||
.. note:: All remote operation requests such as the above are accumulated (buffered)
|
||||
and not forwarded to the IOC until one of execution methods (:meth:`pend_io`, :meth:`poll`, :meth:`pend_event`, :meth:`flush_io`)
|
||||
is called. This allows several requests to be efficiently sent over the network in one message.
|
||||
|
||||
>>> chan = CaChannel('catest')
|
||||
>>> def connCB(epicsArgs, userArgs):
|
||||
... chid = epicsArgs[0]
|
||||
... connection_state = epicsArgs[1]
|
||||
... if connection_state == ca.CA_OP_CONN_UP:
|
||||
... print('%s is connected' % ca.name(chid))
|
||||
>>> chan.search_and_connect(None, connCB, chan)
|
||||
>>> status = chan.pend_event(2)
|
||||
catest is connected
|
||||
"""
|
||||
if pvName is None:
|
||||
pvName = self.pvname
|
||||
else:
|
||||
self.pvname = pvName
|
||||
self._callbacks['connCB']=(callback, user_args)
|
||||
try:
|
||||
self.__chid = ca.search(pvName, self._conn_callback)
|
||||
except ca.error:
|
||||
msg = sys.exc_info()[1]
|
||||
raise CaChannelException(msg)
|
||||
|
||||
def search(self, pvName=None):
|
||||
"""Attempt to establish a connection to a process variable.
|
||||
|
||||
:param str pvName: process variable name
|
||||
:raises CaChannelException: if error happens
|
||||
|
||||
.. note:: All remote operation requests such as the above are accumulated (buffered)
|
||||
and not forwarded to the IOC until one of execution methods (:meth:`pend_io`, :meth:`poll`, :meth:`pend_event`, :meth:`flush_io`)
|
||||
is called. This allows several requests to be efficiently sent over the network in one message.
|
||||
|
||||
>>> chan = CaChannel()
|
||||
>>> chan.search('catest')
|
||||
>>> status = chan.pend_io(1)
|
||||
>>> chan.state()
|
||||
2
|
||||
"""
|
||||
if pvName is None:
|
||||
pvName = self.pvname
|
||||
else:
|
||||
self.pvname = pvName
|
||||
try:
|
||||
self.__chid = ca.search(pvName, None)
|
||||
except ca.error:
|
||||
msg = sys.exc_info()[1]
|
||||
raise CaChannelException(msg)
|
||||
|
||||
def clear_channel(self):
|
||||
"""Close a channel created by one of the search functions.
|
||||
|
||||
Clearing a channel does not cause its connection handler to be called.
|
||||
Clearing a channel does remove any monitors registered for that channel.
|
||||
If the channel is currently connected then resources are freed only some
|
||||
time after this request is flushed out to the server.
|
||||
|
||||
.. note:: All remote operation requests such as the above are accumulated (buffered)
|
||||
and not forwarded to the IOC until one of execution methods (:meth:`pend_io`, :meth:`poll`, :meth:`pend_event`, :meth:`flush_io`)
|
||||
is called. This allows several requests to be efficiently sent over the network in one message.
|
||||
|
||||
"""
|
||||
if(self.__chid is not None):
|
||||
try:
|
||||
ca.clear(self.__chid)
|
||||
except ca.error:
|
||||
msg = sys.exc_info()[1]
|
||||
raise CaChannelException(msg)
|
||||
|
||||
#
|
||||
# Write methods
|
||||
# array_put
|
||||
# array_put_callback
|
||||
#
|
||||
|
||||
def _setup_put(self,value, req_type, count = None):
|
||||
if count is None:
|
||||
count = self.element_count()
|
||||
else:
|
||||
count = max(1, min(self.element_count(), count) )
|
||||
|
||||
if req_type == -1:
|
||||
req_type = self.field_type()
|
||||
|
||||
# single numeric value
|
||||
if (isinstance(value, int) or
|
||||
isinstance(value, long) or
|
||||
isinstance(value, float) or
|
||||
isinstance(value, bool)):
|
||||
pval = (CaChannel.dbr_d[req_type](value),)
|
||||
# single string value
|
||||
# if DBR_CHAR, split into chars
|
||||
# otherwise convert to field type
|
||||
elif isinstance(value, str):
|
||||
if req_type == ca.DBR_CHAR:
|
||||
if len(value) < count:
|
||||
count = len(value)
|
||||
pval = [ord(x) for x in value[:count]]
|
||||
else:
|
||||
pval = (CaChannel.dbr_d[req_type](value),)
|
||||
# assumes other sequence type
|
||||
else:
|
||||
if len(value) < count:
|
||||
count = len(value)
|
||||
pval = [CaChannel.dbr_d[req_type](x) for x in value[:count]]
|
||||
|
||||
return pval
|
||||
|
||||
def array_put(self, value, req_type=None, count=None):
|
||||
"""Write a value or array of values to a channel
|
||||
|
||||
:param value: data to be written. For multiple values use a list or tuple
|
||||
:param req_type: database request type (``ca.DBR_XXXX``). Defaults to be the native data type.
|
||||
:param int count: number of data values to write. Defaults to be the native count.
|
||||
|
||||
>>> chan = CaChannel('catest')
|
||||
>>> chan.searchw()
|
||||
>>> chan.array_put(123)
|
||||
>>> chan.flush_io()
|
||||
>>> chan.getw()
|
||||
123.0
|
||||
>>> chan = CaChannel('cabo')
|
||||
>>> chan.searchw()
|
||||
>>> chan.array_put('Busy', ca.DBR_STRING)
|
||||
>>> chan.flush_io()
|
||||
>>> chan.getw()
|
||||
1
|
||||
>>> chan = CaChannel('cawave')
|
||||
>>> chan.searchw()
|
||||
>>> chan.array_put([1,2,3])
|
||||
>>> chan.flush_io()
|
||||
>>> chan.getw()
|
||||
[1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
>>> chan.getw(count=3, use_numpy=True)
|
||||
array([ 1., 2., 3.])
|
||||
>>> chan = CaChannel('cawavec')
|
||||
>>> chan.searchw()
|
||||
>>> chan.array_put('1234',count=3)
|
||||
>>> chan.flush_io()
|
||||
>>> chan.getw(count=4)
|
||||
[49, 50, 51, 0]
|
||||
"""
|
||||
if req_type is None: req_type = -1
|
||||
val = self._setup_put(value, req_type, count)
|
||||
try:
|
||||
ca.put(self.__chid, val, None, None, req_type)
|
||||
except ca.error:
|
||||
msg = sys.exc_info()[1]
|
||||
raise CaChannelException(msg)
|
||||
|
||||
def array_put_callback(self, value, req_type, count, callback, *user_args):
|
||||
"""Write a value or array of values to a channel and execute the user
|
||||
supplied callback after the put has completed.
|
||||
|
||||
:param value: data to be written. For multiple values use a list or tuple.
|
||||
:param req_type: database request type (``ca.DBR_XXXX``). Defaults to be the native data type.
|
||||
:param int count: number of data values to write, Defaults to be the native count.
|
||||
:param callable callback: function called when the write is completed.
|
||||
:param user_args: user provided arguments that are passed to callback when it is invoked.
|
||||
:raises CaChannelException: if error happens
|
||||
|
||||
Each Python callback function is required to have two arguments.
|
||||
The first argument is a dictionary containing the results of the action.
|
||||
|
||||
======= ===== =======
|
||||
field type comment
|
||||
======= ===== =======
|
||||
chid int channels id structure
|
||||
type int database request type (ca.DBR_XXXX)
|
||||
count int number of values to transfered
|
||||
status int CA status return code (ca.ECA_XXXX)
|
||||
======= ===== =======
|
||||
|
||||
The second argument is a tuple containing any user arguments specified by ``user_args``.
|
||||
If no arguments were specified then the tuple is empty.
|
||||
|
||||
|
||||
>>> def putCB(epicsArgs, userArgs):
|
||||
... print('%s put completed' % ca.name(epicsArgs['chid']))
|
||||
>>> chan = CaChannel('catest')
|
||||
>>> chan.searchw()
|
||||
>>> chan.array_put_callback(145, None, None, putCB)
|
||||
>>> status = chan.pend_event(1)
|
||||
catest put completed
|
||||
>>> chan = CaChannel('cabo')
|
||||
>>> chan.searchw()
|
||||
>>> chan.array_put_callback('Busy', ca.DBR_STRING, None, putCB)
|
||||
>>> status = chan.pend_event(1)
|
||||
cabo put completed
|
||||
>>> chan = CaChannel('cawave')
|
||||
>>> chan.searchw()
|
||||
>>> chan.array_put_callback([1,2,3], None, None, putCB)
|
||||
>>> status = chan.pend_event(1)
|
||||
cawave put completed
|
||||
>>> chan = CaChannel('cawavec')
|
||||
>>> chan.searchw()
|
||||
>>> chan.array_put_callback('123', None, None, putCB)
|
||||
>>> status = chan.pend_event(1)
|
||||
cawavec put completed
|
||||
"""
|
||||
if req_type is None: req_type = 0
|
||||
val = self._setup_put(value, req_type, count)
|
||||
self._callbacks['putCB']=(callback, user_args)
|
||||
try:
|
||||
ca.put(self.__chid, val, None, self._put_callback, req_type)
|
||||
except ca.error:
|
||||
msg = sys.exc_info()[1]
|
||||
raise CaChannelException(msg)
|
||||
#
|
||||
# Read methods
|
||||
# getValue
|
||||
# array_get
|
||||
# array_get_callback
|
||||
#
|
||||
|
||||
# Obtain read value after ECA_NORMAL is returned on an array_get().
|
||||
def getValue(self):
|
||||
"""Return the value(s) after array_get has completed"""
|
||||
return self.val
|
||||
|
||||
# Simulate with a synchronous getw function call
|
||||
def array_get(self, req_type=None, count=None, **keywords):
|
||||
"""Read a value or array of values from a channel. The new value is
|
||||
retrieved by a call to getValue method.
|
||||
|
||||
:param req_type: database request type (``ca.DBR_XXXX``). Defaults to be the native data type.
|
||||
:param int count: number of data values to read, Defaults to be the native count.
|
||||
:param keywords: optional arguments assigned by keywords
|
||||
|
||||
=========== =====
|
||||
keyword value
|
||||
=========== =====
|
||||
use_numpy True if waveform should be returned as numpy array. Default :data:`CaChannel.USE_NUMPY`.
|
||||
=========== =====
|
||||
|
||||
:raises CaChannelException: if error happens
|
||||
|
||||
.. note:: All remote operation requests such as the above are accumulated (buffered)
|
||||
and not forwarded to the IOC until one of execution methods (``pend_io``, ``poll``, ``pend_event``, ``flush_io``)
|
||||
is called. This allows several requests to be efficiently sent over the network in one message.
|
||||
|
||||
|
||||
>>> chan = CaChannel('catest')
|
||||
>>> chan.searchw()
|
||||
>>> chan.putw(123)
|
||||
>>> chan.array_get()
|
||||
>>> chan.getValue()
|
||||
123.0
|
||||
"""
|
||||
self.val = self.getw(req_type, count, **keywords)
|
||||
|
||||
def array_get_callback(self, req_type, count, callback, *user_args, **keywords):
|
||||
"""Read a value or array of values from a channel and execute the user
|
||||
supplied callback after the get has completed.
|
||||
|
||||
:param req_type: database request type (``ca.DBR_XXXX``). Defaults to be the native data type.
|
||||
:param int count: number of data values to read, Defaults to be the native count.
|
||||
:param callable callback: function called when the get is completed.
|
||||
:param user_args: user provided arguments that are passed to callback when it is invoked.
|
||||
:param keywords: optional arguments assigned by keywords
|
||||
|
||||
=========== =====
|
||||
keyword value
|
||||
=========== =====
|
||||
use_numpy True if waveform should be returned as numpy array. Default :data:`CaChannel.USE_NUMPY`.
|
||||
=========== =====
|
||||
|
||||
:raises CaChannelException: if error happens
|
||||
|
||||
Each Python callback function is required to have two arguments.
|
||||
The first argument is a dictionary containing the results of the action.
|
||||
|
||||
+-----------------+---------------+------------------------------------+-------------------------+---------------+-------------+---------------+
|
||||
| field | type | comment | request type |
|
||||
| | | +----------+--------------+---------------+-------------+---------------+
|
||||
| | | | DBR_XXXX | DBR_STS_XXXX | DBR_TIME_XXXX | DBR_GR_XXXX | DBR_CTRL_XXXX |
|
||||
+=================+===============+====================================+==========+==============+===============+=============+===============+
|
||||
| chid | int | channels id number | X | X | X | X | X |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| type | int | database request type | X | X | X | X | X |
|
||||
| | | (ca.DBR_XXXX) | | | | | |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| count | int | number of values to transfered | X | X | X | X | X |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| status | int | CA status return code | X | X | X | X | X |
|
||||
| | | (ca.ECA_XXXX) | | | | | |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| pv_value | | PV value | X | X | X | X | X |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| pv_status | int | PV alarm status | | X | X | X | X |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| pv_severity | int | PV alarm severity | | X | X | X | X |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| pv_seconds | float | timestamp | | | X | | |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| pv_nostrings | int | ENUM PV's number of states | | | | X | X |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| pv_statestrings | string list | ENUM PV's states string | | | | X | X |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| pv_units | string | units | | | | X | X |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| pv_precision | int | precision | | | | X | X |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| pv_updislim | float | upper display limit | | | | X | X |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| pv_lodislim | float | lower display limit | | | | X | X |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| pv_upalarmlim | float | upper alarm limit | | | | X | X |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| pv_upwarnlim | float | upper warning limit | | | | X | X |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| pv_loalarmlim | float | lower alarm limit | | | | X | X |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| pv_lowarnlim | float | lower warning limit | | | | X | X |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| pv_upctrllim | float | upper control limit | | | | | X |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
| pv_loctrllim | float | lower control limit | | | | | X |
|
||||
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
|
||||
|
||||
The second argument is a tuple containing any user arguments specified by ``user_args``.
|
||||
If no arguments were specified then the tuple is empty.
|
||||
|
||||
.. note:: All remote operation requests such as the above are accumulated (buffered)
|
||||
and not forwarded to the IOC until one of execution methods (``pend_io``, ``poll``, ``pend_event``, ``flush_io``)
|
||||
is called. This allows several requests to be efficiently sent over the network in one message.
|
||||
|
||||
>>> def getCB(epicsArgs, userArgs):
|
||||
... for item in sorted(epicsArgs.keys()):
|
||||
... if item.startswith('pv_'):
|
||||
... print('%s %s' % (item,epicsArgs[item]))
|
||||
>>> chan = CaChannel('catest')
|
||||
>>> chan.searchw()
|
||||
>>> chan.putw(145)
|
||||
>>> chan.array_get_callback(ca.DBR_CTRL_DOUBLE, 1, getCB)
|
||||
>>> status = chan.pend_event(1)
|
||||
pv_loalarmlim -20.0
|
||||
pv_loctrllim 0.0
|
||||
pv_lodislim 0.0
|
||||
pv_lowarnlim -10.0
|
||||
pv_precision 3
|
||||
pv_severity 2
|
||||
pv_status 3
|
||||
pv_units mm
|
||||
pv_upalarmlim 20.0
|
||||
pv_upctrllim 0.0
|
||||
pv_updislim 0.0
|
||||
pv_upwarnlim 10.0
|
||||
pv_value 145.0
|
||||
>>> chan = CaChannel('cabo')
|
||||
>>> chan.searchw()
|
||||
>>> chan.putw(0)
|
||||
>>> chan.array_get_callback(ca.DBR_CTRL_ENUM, 1, getCB)
|
||||
>>> status = chan.pend_event(1)
|
||||
pv_nostrings 2
|
||||
pv_severity 0
|
||||
pv_statestrings ('Done', 'Busy')
|
||||
pv_status 0
|
||||
pv_value 0
|
||||
"""
|
||||
if req_type is None: req_type = ca.dbf_type_to_DBR(self.field_type())
|
||||
if count is None: count = self.element_count()
|
||||
self._callbacks['getCB']=(callback, user_args)
|
||||
try:
|
||||
ca.get(self.__chid, self._get_callback, req_type, count, keywords.get('use_numpy', USE_NUMPY))
|
||||
except ca.error:
|
||||
msg = sys.exc_info()[1]
|
||||
raise CaChannelException(msg)
|
||||
|
||||
#
|
||||
# Monitor methods
|
||||
# add_masked_array_event
|
||||
# clear_event
|
||||
#
|
||||
|
||||
# Creates a new event id and stores it on self.__evid. Only one event registered
|
||||
# per CaChannel object. If an event is already registered the event is cleared
|
||||
# before registering a new event.
|
||||
def add_masked_array_event(self, req_type, count, mask, callback, *user_args, **keywords):
|
||||
"""Specify a callback function to be executed whenever changes occur to a PV.
|
||||
|
||||
:param req_type: database request type (``ca.DBR_XXXX``). Defaults to be the native data type.
|
||||
:param int count: number of data values to read, Defaults to be the native count.
|
||||
:param mask: logical or of ``ca.DBE_VALUE``, ``ca.DBE_LOG``, ``ca.DBE_ALARM``.
|
||||
Defaults to be ``ca.DBE_VALUE|ca.DBE_ALARM``.
|
||||
:param callable callback: function called when the get is completed.
|
||||
:param user_args: user provided arguments that are passed to callback when
|
||||
it is invoked.
|
||||
:param keywords: optional arguments assigned by keywords
|
||||
|
||||
=========== =====
|
||||
keyword value
|
||||
=========== =====
|
||||
use_numpy True if waveform should be returned as numpy array. Default :data:`CaChannel.USE_NUMPY`.
|
||||
=========== =====
|
||||
|
||||
:raises CaChannelException: if error happens
|
||||
|
||||
.. note:: All remote operation requests such as the above are accumulated (buffered)
|
||||
and not forwarded to the IOC until one of execution methods (:meth:`pend_io`, :meth:`poll`, :meth:`pend_event`, :meth:`flush_io`)
|
||||
is called. This allows several requests to be efficiently sent over the network in one message.
|
||||
|
||||
>>> def eventCB(epicsArgs, userArgs):
|
||||
... print('pv_value %s' % epicsArgs['pv_value'])
|
||||
... print('pv_status %d %s' % (epicsArgs['pv_status'], ca.alarmStatusString(epicsArgs['pv_status'])))
|
||||
... print('pv_severity %d %s' % (epicsArgs['pv_severity'], ca.alarmSeverityString(epicsArgs['pv_severity'])))
|
||||
>>> chan = CaChannel('cabo')
|
||||
>>> chan.searchw()
|
||||
>>> chan.putw(1)
|
||||
>>> chan.add_masked_array_event(ca.DBR_STS_ENUM, None, None, eventCB)
|
||||
>>> status = chan.pend_event(1)
|
||||
pv_value 1
|
||||
pv_status 7 STATE
|
||||
pv_severity 1 MINOR
|
||||
>>> chan.clear_event()
|
||||
>>> chan.add_masked_array_event(ca.DBR_STS_STRING, None, None, eventCB)
|
||||
>>> status = chan.pend_event(1)
|
||||
pv_value Busy
|
||||
pv_status 7 STATE
|
||||
pv_severity 1 MINOR
|
||||
>>> chan.clear_event()
|
||||
"""
|
||||
if req_type is None: req_type = ca.dbf_type_to_DBR(self.field_type())
|
||||
if count is None: count = self.element_count()
|
||||
if mask is None: mask = ca.DBE_VALUE|ca.DBE_ALARM
|
||||
if self.__evid is not None:
|
||||
self.clear_event()
|
||||
self.flush_io()
|
||||
self._callbacks['eventCB']=(callback, user_args)
|
||||
try:
|
||||
self.__evid = ca.monitor(self.__chid, self._event_callback, count, mask, req_type, keywords.get('use_numpy', USE_NUMPY))
|
||||
except ca.error:
|
||||
msg = sys.exc_info()[1]
|
||||
raise CaChannelException(msg)
|
||||
|
||||
def clear_event(self):
|
||||
"""Remove previously installed callback function.
|
||||
|
||||
.. note:: All remote operation requests such as the above are accumulated (buffered)
|
||||
and not forwarded to the IOC until one of execution methods (:meth:`pend_io`, :meth:`poll`, :meth:`pend_event`, :meth:`flush_io`)
|
||||
is called. This allows several requests to be efficiently sent over the network in one message.
|
||||
"""
|
||||
if self.__evid is not None:
|
||||
try:
|
||||
ca.clear_monitor(self.__evid)
|
||||
self.__evid = None
|
||||
except ca.error:
|
||||
msg = sys.exc_info()[1]
|
||||
raise CaChannelException(msg)
|
||||
|
||||
#
|
||||
# Execute methods
|
||||
# pend_io
|
||||
# pend_event
|
||||
# poll
|
||||
# flush_io
|
||||
#
|
||||
|
||||
def pend_io(self,timeout=None):
|
||||
"""Flush the send buffer and wait until outstanding queries (``search``, ``array_get``) complete
|
||||
or the specified timeout expires.
|
||||
|
||||
:param float timeout: seconds to wait
|
||||
:raises CaChannelException: if timeout or other error happens
|
||||
|
||||
"""
|
||||
if timeout is None:
|
||||
timeout = self.getTimeout()
|
||||
status = ca.pend_io(float(timeout))
|
||||
if status != 0:
|
||||
raise CaChannelException(ca.caError._caErrorMsg[status])
|
||||
|
||||
def pend_event(self,timeout=None):
|
||||
"""Flush the send buffer and process background activity (connect/get/put/monitor callbacks) for ``timeout`` seconds.
|
||||
|
||||
It will not return before the specified timeout expires and all unfinished channel access labor has been processed.
|
||||
|
||||
:param float timeout: seconds to wait
|
||||
|
||||
"""
|
||||
if timeout is None:
|
||||
timeout = 0.1
|
||||
status = ca.pend_event(timeout)
|
||||
# status is always ECA_TIMEOUT
|
||||
return status
|
||||
|
||||
def poll(self):
|
||||
"""Flush the send buffer and execute any outstanding background activity.
|
||||
|
||||
.. note:: It is an alias to ``pend_event(1e-12)``.
|
||||
"""
|
||||
status = ca.poll()
|
||||
# status is always ECA_TIMEOUT
|
||||
return status
|
||||
|
||||
def flush_io(self):
|
||||
"""Flush the send buffer and does not execute outstanding background activity."""
|
||||
status = ca.flush()
|
||||
if status != 0:
|
||||
raise CaChannelException(ca.caError._caErrorMsg[status])
|
||||
|
||||
#
|
||||
# Channel Access Macros
|
||||
# field_type
|
||||
# element_count
|
||||
# name
|
||||
# state
|
||||
# host_name
|
||||
# read_access
|
||||
# write_access
|
||||
#
|
||||
def get_info(self):
|
||||
try:
|
||||
info=(self._field_type, self._element_count, self._puser,
|
||||
self._conn_state, self._host_name, self._raccess,
|
||||
self._waccess) = ca.ch_info(self.__chid)
|
||||
except ca.error:
|
||||
msg = sys.exc_info()[1]
|
||||
raise CaChannelException(msg)
|
||||
return info
|
||||
|
||||
|
||||
def field_type(self):
|
||||
"""Native type of the PV in the server (``ca.DBF_XXXX``).
|
||||
|
||||
>>> chan = CaChannel('catest')
|
||||
>>> chan.searchw()
|
||||
>>> ftype = chan.field_type()
|
||||
>>> ftype
|
||||
6
|
||||
>>> ca.dbf_text(ftype)
|
||||
'DBF_DOUBLE'
|
||||
>>> ca.DBF_DOUBLE == ftype
|
||||
True
|
||||
"""
|
||||
self.get_info()
|
||||
return self._field_type
|
||||
|
||||
def element_count(self):
|
||||
"""Maximum array element count of the PV in the server.
|
||||
|
||||
>>> chan = CaChannel('catest')
|
||||
>>> chan.searchw()
|
||||
>>> chan.element_count()
|
||||
1
|
||||
"""
|
||||
self.get_info()
|
||||
return self._element_count
|
||||
|
||||
def name(self):
|
||||
"""Channel name specified when the channel was created.
|
||||
|
||||
>>> chan = CaChannel('catest')
|
||||
>>> chan.searchw()
|
||||
>>> chan.name()
|
||||
'catest'
|
||||
"""
|
||||
return ca.name(self.__chid)
|
||||
|
||||
def state(self):
|
||||
"""Current state of the CA connection.
|
||||
|
||||
================== =============
|
||||
States Meaning
|
||||
================== =============
|
||||
ca.cs_never_conn PV not found
|
||||
ca.cs_prev_conn PV was found but unavailable
|
||||
ca.cs_conn PV was found and available
|
||||
ca.cs_closed PV not closed
|
||||
ca.cs_never_search PV not searched yet
|
||||
================== =============
|
||||
|
||||
>>> chan = CaChannel('catest')
|
||||
>>> chan.searchw()
|
||||
>>> chan.state()
|
||||
2
|
||||
"""
|
||||
if self.__chid is None:
|
||||
return ca.cs_never_search
|
||||
else:
|
||||
self.get_info()
|
||||
return self._conn_state
|
||||
|
||||
def host_name(self):
|
||||
"""Host name that hosts the process variable."""
|
||||
self.get_info()
|
||||
return self._host_name
|
||||
|
||||
def read_access(self):
|
||||
"""Access right to read the channel.
|
||||
|
||||
:return: True if the channel can be read, False otherwise.
|
||||
|
||||
"""
|
||||
self.get_info()
|
||||
return self._raccess
|
||||
|
||||
def write_access(self):
|
||||
"""Access right to write the channel.
|
||||
|
||||
:return: True if the channel can be written, False otherwise.
|
||||
|
||||
"""
|
||||
self.get_info()
|
||||
return self._waccess
|
||||
#
|
||||
# Wait functions
|
||||
#
|
||||
# These functions wait for completion of the requested action.
|
||||
def searchw(self, pvName=None):
|
||||
"""Attempt to establish a connection to a process variable.
|
||||
|
||||
:param str pvName: process variable name
|
||||
:raises CaChannelException: if timeout or error happens
|
||||
|
||||
.. note:: This method waits for connection to be established or fail with exception.
|
||||
|
||||
>>> chan = CaChannel('non-exist-channel')
|
||||
>>> chan.searchw()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
CaChannelException: User specified timeout on IO operation expired
|
||||
"""
|
||||
if pvName is None:
|
||||
pvName = self.pvname
|
||||
else:
|
||||
self.pvname = pvName
|
||||
self.__chid = ca.search(pvName, None)
|
||||
timeout = self.getTimeout()
|
||||
status = ca.pend_io(timeout)
|
||||
if status != 0:
|
||||
raise CaChannelException(ca.caError._caErrorMsg[status])
|
||||
|
||||
def putw(self, value, req_type=None):
|
||||
"""Write a value or array of values to a channel
|
||||
|
||||
If the request type is omitted the data is written as the Python type corresponding to the native format.
|
||||
Multi-element data is specified as a tuple or a list.
|
||||
Internally the sequence is converted to a list before inserting the values into a C array.
|
||||
Access using non-numerical types is restricted to the first element in the data field.
|
||||
Mixing character types with numerical types writes bogus results but is not prohibited at this time.
|
||||
DBF_ENUM fields can be written using DBR_ENUM and DBR_STRING types.
|
||||
DBR_STRING writes of a field of type DBF_ENUM must be accompanied by a valid string out of the possible enumerated values.
|
||||
|
||||
:param value: data to be written. For multiple values use a list or tuple
|
||||
:param req_type: database request type (``ca.DBR_XXXX``). Defaults to be the native data type.
|
||||
:raises CaChannelException: if timeout or error happens
|
||||
|
||||
.. note:: This method does flush the request to the channel access server.
|
||||
|
||||
>>> chan = CaChannel('catest')
|
||||
>>> chan.searchw()
|
||||
>>> chan.putw(145)
|
||||
>>> chan.getw()
|
||||
145.0
|
||||
>>> chan = CaChannel('cabo')
|
||||
>>> chan.searchw()
|
||||
>>> chan.putw('Busy', ca.DBR_STRING)
|
||||
>>> chan.getw()
|
||||
1
|
||||
>>> chan.getw(ca.DBR_STRING)
|
||||
'Busy'
|
||||
>>> chan = CaChannel('cawave')
|
||||
>>> chan.searchw()
|
||||
>>> chan.putw([1,2,3])
|
||||
>>> chan.getw(req_type=ca.DBR_LONG,count=4)
|
||||
[1, 2, 3, 0]
|
||||
>>> chan = CaChannel('cawavec')
|
||||
>>> chan.searchw()
|
||||
>>> chan.putw('123')
|
||||
>>> chan.getw(count=4)
|
||||
[49, 50, 51, 0]
|
||||
>>> chan = CaChannel('cawaves')
|
||||
>>> chan.searchw()
|
||||
>>> chan.putw(['string 1','string 2'])
|
||||
>>> chan.getw()
|
||||
['string 1', 'string 2', '']
|
||||
"""
|
||||
if req_type is None: req_type = -1
|
||||
val = self._setup_put(value, req_type)
|
||||
try:
|
||||
ca.put(self.__chid, val, None, None, req_type)
|
||||
except ca.error:
|
||||
msg = sys.exc_info()[1]
|
||||
raise CaChannelException(msg)
|
||||
self.flush_io()
|
||||
|
||||
def getw(self, req_type=None, count=None, **keywords):
|
||||
"""Read the value from a channel.
|
||||
|
||||
:param req_type: database request type. Defaults to be the native data type.
|
||||
:param int count: number of data values to read, Defaults to be the native count.
|
||||
:param keywords: optional arguments assigned by keywords
|
||||
|
||||
=========== =====
|
||||
keyword value
|
||||
=========== =====
|
||||
use_numpy True if waveform should be returned as numpy array. Default :data:`CaChannel.USE_NUMPY`.
|
||||
=========== =====
|
||||
:return: If req_type is plain request type, only the value is returned. Otherwise a dict returns
|
||||
with information depending on the request type, same as the first argument passed to user's callback.
|
||||
See :meth:`array_get_callback`.
|
||||
|
||||
:raises CaChannelException: if timeout error happens
|
||||
|
||||
If the request type is omitted the data is returned to the user as the Python type corresponding to the native format.
|
||||
Multi-element data has all the elements returned as items in a list and must be accessed using a numerical type.
|
||||
Access using non-numerical types is restricted to the first element in the data field.
|
||||
DBF_ENUM fields can be read using DBR_ENUM and DBR_STRING types.
|
||||
DBR_STRING reads of a field of type DBF_ENUM returns the string corresponding to the current enumerated value.
|
||||
|
||||
"""
|
||||
updated = [False]
|
||||
value = [0]
|
||||
def update_value(args):
|
||||
if args is None:
|
||||
return
|
||||
try:
|
||||
value[0] = self._format_cb_args(args)
|
||||
finally:
|
||||
updated[0] = True
|
||||
if req_type is None: req_type = ca.dbf_type_to_DBR(self.field_type())
|
||||
if count is None: count = self.element_count()
|
||||
try:
|
||||
ca.get(self.__chid, update_value, req_type, count, keywords.get('use_numpy', USE_NUMPY))
|
||||
except ca.error:
|
||||
msg = sys.exc_info()[1]
|
||||
raise CaChannelException(msg)
|
||||
timeout = self.getTimeout()
|
||||
self.flush_io()
|
||||
n = timeout / 0.001
|
||||
while n > 0 and not updated[0]:
|
||||
ca.pend_event(0.001)
|
||||
n-=1
|
||||
if not updated[0]:
|
||||
raise CaChannelException(ca.caError._caErrorMsg[10]) # ECA_TIMEOUT
|
||||
if ca.dbr_type_is_plain(req_type):
|
||||
return value[0]['pv_value']
|
||||
else:
|
||||
return value[0]
|
||||
|
||||
#
|
||||
# Callback functions
|
||||
#
|
||||
# These functions hook user supplied callback functions to CA extension
|
||||
|
||||
def _conn_callback(self):
|
||||
callback = self._callbacks.get('connCB')
|
||||
if callback is None:
|
||||
return
|
||||
callbackFunc, userArgs = callback
|
||||
if self.state() == 2: OP = 6
|
||||
else: OP = 7
|
||||
epicsArgs = (self.__chid, OP)
|
||||
try:
|
||||
callbackFunc(epicsArgs, userArgs)
|
||||
except:
|
||||
pass
|
||||
|
||||
def _put_callback(self, args):
|
||||
callback = self._callbacks.get('putCB')
|
||||
if callback is None:
|
||||
return
|
||||
callbackFunc, userArgs = callback
|
||||
epicsArgs={}
|
||||
epicsArgs['chid']=self.__chid
|
||||
epicsArgs['type']=self.field_type()
|
||||
epicsArgs['count']=self.element_count()
|
||||
epicsArgs['status']=args[1]
|
||||
try:
|
||||
callbackFunc(epicsArgs, userArgs)
|
||||
except:
|
||||
pass
|
||||
|
||||
def _get_callback(self, args):
|
||||
callback = self._callbacks.get('getCB')
|
||||
if callback is None:
|
||||
return
|
||||
callbackFunc, userArgs = callback
|
||||
epicsArgs = self._format_cb_args(args)
|
||||
try:
|
||||
callbackFunc(epicsArgs, userArgs)
|
||||
except:
|
||||
pass
|
||||
|
||||
def _event_callback(self, args):
|
||||
callback = self._callbacks.get('eventCB')
|
||||
if callback is None:
|
||||
return
|
||||
callbackFunc, userArgs = callback
|
||||
epicsArgs = self._format_cb_args(args)
|
||||
try:
|
||||
callbackFunc(epicsArgs, userArgs)
|
||||
except:
|
||||
pass
|
||||
|
||||
def _format_cb_args(self, args):
|
||||
epicsArgs={}
|
||||
epicsArgs['chid'] = self.__chid
|
||||
# dbr_type is not returned
|
||||
# use dbf_type instead
|
||||
epicsArgs['type'] = self.field_type()
|
||||
epicsArgs['count'] = self.element_count()
|
||||
# status flag is not returned,
|
||||
# args[1] is alarm status
|
||||
# assume ECA_NORMAL
|
||||
epicsArgs['status'] = 1
|
||||
if len(args)==2: # Error
|
||||
epicsArgs['pv_value'] = args[0] # always None
|
||||
epicsArgs['status'] = args[1]
|
||||
if len(args)>=3: # DBR_Plain
|
||||
epicsArgs['pv_value'] = args[0]
|
||||
epicsArgs['pv_severity']= args[1]
|
||||
epicsArgs['pv_status'] = args[2]
|
||||
if len(args)==4: # DBR_TIME, 0.0 for others
|
||||
epicsArgs['pv_seconds'] = args[3]
|
||||
if len(args)==5:
|
||||
if len(args[4])==2: # DBR_CTRL_ENUM
|
||||
epicsArgs['pv_nostrings'] = args[4][0]
|
||||
epicsArgs['pv_statestrings']= args[4][1]
|
||||
if len(args[4])>=7: # DBR_GR
|
||||
epicsArgs['pv_units'] = args[4][0]
|
||||
epicsArgs['pv_updislim'] = args[4][1]
|
||||
epicsArgs['pv_lodislim'] = args[4][2]
|
||||
epicsArgs['pv_upalarmlim'] = args[4][3]
|
||||
epicsArgs['pv_upwarnlim'] = args[4][4]
|
||||
epicsArgs['pv_loalarmlim'] = args[4][5]
|
||||
epicsArgs['pv_lowarnlim'] = args[4][6]
|
||||
if len(args[4])==8: # DBR_GR_FLOAT or DBR_GR_DOUBLE
|
||||
epicsArgs['pv_precision'] = args[4][7]
|
||||
if len(args[4])>=9: # DBR_CTRL
|
||||
epicsArgs['pv_upctrllim'] = args[4][7]
|
||||
epicsArgs['pv_loctrllim'] = args[4][8]
|
||||
if len(args[4])==10: # DBR_CTRL_FLOAT or DBR_CTRL_DOUBLE
|
||||
epicsArgs['pv_precision'] = args[4][9]
|
||||
return epicsArgs
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
@@ -0,0 +1,212 @@
|
||||
import sys
|
||||
import traceback
|
||||
import java.lang.System
|
||||
import java.lang.Thread
|
||||
import java.lang.InterruptedException
|
||||
import gov.aps.jca.CAStatus
|
||||
import gov.aps.jca.JCALibrary
|
||||
import gov.aps.jca.configuration.DefaultConfiguration
|
||||
import gov.aps.jca.dbr.DBRType
|
||||
import gov.aps.jca.dbr.Severity
|
||||
import gov.aps.jca.event.PutListener
|
||||
import gov.aps.jca.event.GetListener
|
||||
import gov.aps.jca.event.MonitorListener
|
||||
import ch.psi.jcae.impl.JcaeProperties
|
||||
|
||||
version = "PShell wrapper"
|
||||
release = "1.0.0"
|
||||
revision = "1.0.0"
|
||||
error = Exception
|
||||
|
||||
def dbf_type_is_valid(type):
|
||||
return (type >= 0) and (type <= LAST_TYPE)
|
||||
def dbr_type_is_valid(type):
|
||||
return (type >= 0) and (type <= LAST_BUFFER_TYPE)
|
||||
def dbr_type_is_plain(type):
|
||||
return (type >= DBR_STRING) and (type <= DBR_DOUBLE)
|
||||
def dbr_type_is_STS(type):
|
||||
return (type >= DBR_STS_STRING) and (type <= DBR_STS_DOUBLE)
|
||||
def dbr_type_is_TIME(type):
|
||||
return (type >= DBR_TIME_STRING) and (type <= DBR_TIME_DOUBLE)
|
||||
def dbr_type_is_GR(type):
|
||||
return (type >= DBR_GR_STRING) and (type <= DBR_GR_DOUBLE)
|
||||
def dbr_type_is_CTRL(type):
|
||||
return (type >= DBR_CTRL_STRING) and (type <= DBR_CTRL_DOUBLE)
|
||||
def dbr_type_is_STRING(type):
|
||||
return (type >= 0) and (type <= LAST_BUFFER_TYPE) and (type%(LAST_TYPE+1) == DBR_STRING)
|
||||
def dbr_type_is_SHORT(type):
|
||||
return (type >= 0) and (type <= LAST_BUFFER_TYPE) and (type%(LAST_TYPE+1) == DBR_SHORT)
|
||||
def dbr_type_is_FLOAT(type):
|
||||
return (type >= 0) and (type <= LAST_BUFFER_TYPE) and (type%(LAST_TYPE+1) == DBR_FLOAT)
|
||||
def dbr_type_is_ENUM(type):
|
||||
return (type >= 0) and (type <= LAST_BUFFER_TYPE) and (type%(LAST_TYPE+1) == DBR_ENUM)
|
||||
def dbr_type_is_CHAR(type):
|
||||
return (type >= 0) and (type <= LAST_BUFFER_TYPE) and (type%(LAST_TYPE+1) == DBR_CHAR)
|
||||
def dbr_type_is_LONG(type):
|
||||
return (type >= 0) and (type <= LAST_BUFFER_TYPE) and (type%(LAST_TYPE+1) == DBR_LONG)
|
||||
def dbr_type_is_DOUBLE(type):
|
||||
return (type >= 0) and (type <= LAST_BUFFER_TYPE) and (type%(LAST_TYPE+1) == DBR_DOUBLE)
|
||||
|
||||
#I am assuming have JCAE, but it is possible to implement over JCA only, prividing the configuration
|
||||
properties = ch.psi.jcae.impl.JcaeProperties.getInstance()
|
||||
jca= gov.aps.jca.JCALibrary.getInstance()
|
||||
context = None
|
||||
configuration = gov.aps.jca.configuration.DefaultConfiguration("jython ca context")
|
||||
configuration.setAttribute("class", gov.aps.jca.JCALibrary.CHANNEL_ACCESS_JAVA)
|
||||
configuration.setAttribute("addr_list", properties.getAddressList())
|
||||
configuration.setAttribute("auto_addr_list", str(properties.isAutoAddressList()));
|
||||
if properties.getMaxArrayBytes() is not None:
|
||||
configuration.setAttribute("max_array_bytes", properties.getMaxArrayBytes())
|
||||
if properties.getServerPort() is not None:
|
||||
configuration.setAttribute("server_port", properties.getServerPort())
|
||||
|
||||
def initialize():
|
||||
global jca
|
||||
global context
|
||||
global configuration
|
||||
|
||||
if context is not None:
|
||||
context.destroy()
|
||||
|
||||
context= jca.createContext(configuration)
|
||||
|
||||
initialize()
|
||||
|
||||
|
||||
class PutListener(gov.aps.jca.event.PutListener):
|
||||
def __init__(self, callback):
|
||||
self.callback = callback
|
||||
def putCompleted(self, put_ev):
|
||||
if put_ev is None:
|
||||
self.callback(None , None , None)
|
||||
else:
|
||||
count = put_ev.getCount()
|
||||
status = put_ev.getStatus()
|
||||
dbr_type = put_ev.getType()
|
||||
self.callback([count, status, dbr_type]) #TODO: Check these: status must be second par
|
||||
|
||||
def formatCbArgs(status, dbr):
|
||||
dbrType = dbr.getType()
|
||||
if status <> gov.aps.jca.CAStatus.NORMAL:
|
||||
cb_args=[None, status.getValue()]
|
||||
else:
|
||||
try:
|
||||
val = dbr.getValue()
|
||||
if val is not None:
|
||||
val = val.tolist()
|
||||
if len(val) == 1:
|
||||
val = val[0]
|
||||
if dbr.isSTS():
|
||||
cb_args = [val, dbr.getSeverity().getValue(), status.getValue()]
|
||||
else:
|
||||
cb_args = [val, gov.aps.jca.dbr.Severity.NO_ALARM, status.getValue()]
|
||||
if dbr.isTIME():
|
||||
timestamp = dbr.getTimeStamp()
|
||||
cb_args.append( timestamp.nsec() if (timestamp is not None) else 0.0)
|
||||
else:
|
||||
cb_args.append(0.0)
|
||||
if dbr.isENUM():
|
||||
cb_args.append([None, None]) #TODO
|
||||
elif dbr.isGR():
|
||||
gr=[dbr.getUnits(), dbr.getUpperDispLimit(), dbr.getLowerDispLimit(),
|
||||
dbr.getUpperAlarmLimit(), dbr.getUpperWarningLimit(),
|
||||
dbr.getLowerAlarmLimit(), dbr.getLowerWarningLimit()]
|
||||
if (dbr.isCTRL()):
|
||||
gr.append(dbr.getUpperCtrlLimit())
|
||||
gr.append(dbr.getLowerCtrlLimit())
|
||||
if (dbr.isPRECSION()):
|
||||
gr.append(dbr.getPrecision())
|
||||
cb_args.append(gr)
|
||||
except:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
cb_args=[None, None]
|
||||
return cb_args
|
||||
|
||||
|
||||
class GetListener(gov.aps.jca.event.GetListener):
|
||||
def __init__(self, callback):
|
||||
self.callback = callback
|
||||
def getCompleted(self, get_ev):
|
||||
status = get_ev.getStatus()
|
||||
dbr = get_ev.getDBR()
|
||||
self.callback(formatCbArgs(status, dbr))
|
||||
|
||||
class MonitorListener(gov.aps.jca.event.MonitorListener):
|
||||
def __init__(self, callback):
|
||||
self.callback = callback
|
||||
def monitorChanged(self, monitor_ev):
|
||||
status = monitor_ev.getStatus()
|
||||
dbr = monitor_ev.getDBR()
|
||||
self.callback(formatCbArgs(status, dbr))
|
||||
#print dbr.getValue()
|
||||
|
||||
def search(name, callback): #returns channel
|
||||
ch= context.createChannel(name)
|
||||
context.pendIO(1.0)
|
||||
return ch
|
||||
|
||||
def name(channel):
|
||||
return channel.getName()
|
||||
|
||||
def clear(channel):
|
||||
channel.destroy()
|
||||
flush()
|
||||
|
||||
def put(channel, val, not_used, put_callback, req_type):
|
||||
type = gov.aps.jca.dbr.DBRType.forValue(req_type)
|
||||
if put_callback is not None:
|
||||
listener = PutListener(put_callback)
|
||||
channel.put(val, listener)
|
||||
else:
|
||||
channel.put(val)
|
||||
flush()
|
||||
|
||||
def get(channel, get_callback, req_type, count, *args):
|
||||
listener = GetListener(get_callback)
|
||||
type = gov.aps.jca.dbr.DBRType.forValue(req_type)
|
||||
channel.get(type, count, listener)
|
||||
flush()
|
||||
|
||||
|
||||
def monitor(channel, event_callback, count, mask, req_type, *args):
|
||||
listener = MonitorListener(event_callback)
|
||||
type = gov.aps.jca.dbr.DBRType.forValue(req_type)
|
||||
monitor = channel.addMonitor(type, count, mask, listener)
|
||||
flush()
|
||||
return monitor
|
||||
|
||||
def clear_monitor(event_id):
|
||||
event_id.removeMonitorListener(event_id.getMonitorListener())
|
||||
flush()
|
||||
|
||||
def ch_info(channel):
|
||||
return (channel.getFieldType().getValue(), channel.getElementCount() ,
|
||||
None, channel.getConnectionState().getValue(), channel.getHostName(),
|
||||
channel.getReadAccess() , channel.getWriteAccess())
|
||||
|
||||
|
||||
def pend_io(timeout):
|
||||
context.pendIO(timeout)
|
||||
_checkInterrupted()
|
||||
return 0 #for OK
|
||||
|
||||
|
||||
def pend_event(timeout):
|
||||
context.pendEvent(timeout)
|
||||
_checkInterrupted()
|
||||
return 80 #C library always returns ECA_TIMEOUT
|
||||
|
||||
def poll():
|
||||
return pend_event(1e-12)
|
||||
|
||||
def flush():
|
||||
context.flushIO()
|
||||
_checkInterrupted()
|
||||
return 0
|
||||
|
||||
|
||||
def _checkInterrupted():
|
||||
java.lang.Thread.currentThread().sleep(0)
|
||||
if java.lang.Thread.currentThread().isInterrupted():
|
||||
raise java.lang.InterruptedException()
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
|
||||
from _ca import *
|
||||
from cadefs import *
|
||||
import caError
|
||||
|
||||
def alarmSeverityString(sevr):
|
||||
try:
|
||||
return AlarmSeverity.Strings[sevr]
|
||||
except:
|
||||
return "Unkown Severity"
|
||||
|
||||
def alarmStatusString(status):
|
||||
try:
|
||||
return AlarmStatus.Strings[status]
|
||||
except:
|
||||
return "Unknown Alarm"
|
||||
|
||||
def message(status):
|
||||
try:
|
||||
return caError._caErrorMsg[caError.CA_EXTRACT_MSG_NO(status)]
|
||||
except:
|
||||
return str(status)
|
||||
|
||||
def dbf_type_is_valid(dbftype):
|
||||
return dbftype >= 0 and dbftype <= LAST_TYPE
|
||||
def dbr_type_is_valid(dbrtype):
|
||||
return dbrtype >= 0 and dbrtype <= LAST_BUFFER_TYPE
|
||||
|
||||
def dbr_type_is_plain(dbrtype):
|
||||
return (dbrtype >= DBR_STRING and dbrtype <= DBR_DOUBLE)
|
||||
def dbr_type_is_STS(dbrtype):
|
||||
return (dbrtype >= DBR_STS_STRING and dbrtype <= DBR_STS_DOUBLE)
|
||||
def dbr_type_is_TIME(dbrtype):
|
||||
return (dbrtype >= DBR_TIME_STRING and dbrtype <= DBR_TIME_DOUBLE)
|
||||
def dbr_type_is_GR(dbrtype):
|
||||
return (dbrtype >= DBR_GR_STRING and dbrtype <= DBR_GR_DOUBLE)
|
||||
def dbr_type_is_CTRL(dbrtype):
|
||||
return (dbrtype >= DBR_CTRL_STRING and dbrtype <= DBR_CTRL_DOUBLE)
|
||||
|
||||
def dbr_type_is_STRING(dbrtype):
|
||||
return (dbrtype >= 0 and dbrtype <= LAST_BUFFER_TYPE and
|
||||
dbrtype%(LAST_TYPE+1) == DBR_STRING)
|
||||
def dbr_type_is_SHORT(dbrtype):
|
||||
return (dbrtype >= 0 and dbrtype <= LAST_BUFFER_TYPE and
|
||||
dbrtype%(LAST_TYPE+1) == DBR_SHORT)
|
||||
def dbr_type_is_FLOAT(dbrtype):
|
||||
return (dbrtype >= 0 and dbrtype <= LAST_BUFFER_TYPE and
|
||||
dbrtype%(LAST_TYPE+1) == DBR_FLOAT)
|
||||
def dbr_type_is_ENUM(dbrtype):
|
||||
return (dbrtype >= 0 and dbrtype <= LAST_BUFFER_TYPE and
|
||||
dbrtype%(LAST_TYPE+1) == DBR_ENUM)
|
||||
def dbr_type_is_CHAR(dbrtype):
|
||||
return (dbrtype >= 0 and dbrtype <= LAST_BUFFER_TYPE and
|
||||
dbrtype%(LAST_TYPE+1) == DBR_CHAR)
|
||||
def dbr_type_is_LONG(dbrtype):
|
||||
return (dbrtype >= 0 and dbrtype <= LAST_BUFFER_TYPE and
|
||||
dbrtype%(LAST_TYPE+1) == DBR_LONG)
|
||||
def dbr_type_is_DOUBLE(dbrtype):
|
||||
return (dbrtype >= 0 and dbrtype <= LAST_BUFFER_TYPE and
|
||||
dbrtype%(LAST_TYPE+1) == DBR_DOUBLE)
|
||||
|
||||
def dbf_type_to_DBR(dbftype):
|
||||
if dbftype>=0 and dbftype <= LAST_TYPE:
|
||||
return dbftype
|
||||
else:
|
||||
return -1
|
||||
|
||||
def dbf_type_to_DBR_STS(dbftype):
|
||||
if dbftype>=0 and dbftype <= LAST_TYPE:
|
||||
return dbftype + LAST_TYPE+1
|
||||
else:
|
||||
return -1
|
||||
|
||||
def dbf_type_to_DBR_TIME(dbftype):
|
||||
if dbftype>=0 and dbftype <= LAST_TYPE:
|
||||
return dbftype + (LAST_TYPE+1)*2
|
||||
else:
|
||||
return -1
|
||||
def dbf_type_to_DBR_GR(dbftype):
|
||||
if dbftype>=0 and dbftype <= LAST_TYPE:
|
||||
return dbftype + (LAST_TYPE+1)*3
|
||||
else:
|
||||
return -1
|
||||
def dbf_type_to_DBR_CTRL(dbftype):
|
||||
if dbftype>=0 and dbftype <= LAST_TYPE:
|
||||
return dbftype + (LAST_TYPE+1)*4
|
||||
else:
|
||||
return -1
|
||||
@@ -0,0 +1,545 @@
|
||||
#!/usr/bin/env python
|
||||
## @package ca: EPICS-CA interface module for Python.
|
||||
"""CA modlue : EPICS-CA interface module for Python.
|
||||
This module provide a version of EPICS-CA and Python interface.
|
||||
It users C module _ca. _ca module basically maps C-API in EPICS ca library into python. Interface between ca.py and _ca module is subject for change. You should not depend on it. API in ca.py will be preserved in future releases as much as possible.
|
||||
Author: Noboru Yamamoto, KEK, JAPAN. -2007.
|
||||
$Revision: 1.1 $
|
||||
"""
|
||||
from __future__ import print_function
|
||||
__version__ = "$Revision: 1.1 $"
|
||||
# $Source: /cvs/G/EPICS/extensions/src/PythonCA/src/_ca_kek.py,v $
|
||||
#
|
||||
try:
|
||||
import signal
|
||||
except:
|
||||
print("signal module is not avaialble")
|
||||
|
||||
import time,thread,gc,sys,atexit
|
||||
from exceptions import ValueError
|
||||
|
||||
# autGIL is not compatible with Tkinter and wx. So code was removed
|
||||
|
||||
# force thread module to call PyEval_InitThread in it.
|
||||
__foo_lock=thread.allocate_lock()
|
||||
def __foo():
|
||||
"""
|
||||
test function foo
|
||||
|
||||
This function is used to ensure thread module is initialized before
|
||||
loading _ca module.
|
||||
"""
|
||||
global __foo_lock
|
||||
__foo_lock.release()
|
||||
thread.exit_thread()
|
||||
# See Python/Include/ceval.h
|
||||
__foo_lock.acquire()
|
||||
thread.start_new_thread(__foo,()) # __foo release lock
|
||||
__foo_lock.acquire() # make sure threading is activated
|
||||
|
||||
import _ca
|
||||
# version from _ca314.cpp
|
||||
version=_ca.version
|
||||
revision=_ca.release
|
||||
|
||||
# some constants for EPICS channel Access library
|
||||
from cadefs import *
|
||||
from caError import *
|
||||
|
||||
#export pend_xxx routines for global operation
|
||||
pendio =_ca.pendio
|
||||
pend_io=_ca.pendio
|
||||
pend_event=_ca.pend_event
|
||||
poll=_ca.poll
|
||||
poll_event=_ca.poll
|
||||
flush_io=_ca.flush
|
||||
flush=_ca.flush
|
||||
test_io=_ca.test_io # test_io retunrs 42 for IODONE , 43 for IOINPROGRESS
|
||||
add_fd_registration=_ca.add_fd_registration
|
||||
|
||||
#Error Object
|
||||
error=_ca.error
|
||||
shutdown=_ca.__ca_task_exit
|
||||
|
||||
#private dictionary for Get/Put functions
|
||||
|
||||
__ca_dict={}
|
||||
__ca_dict_lock=thread.allocate_lock()
|
||||
_channel__debug=False
|
||||
|
||||
class channel:
|
||||
"""
|
||||
a channel object for EPICS Channel Access.
|
||||
|
||||
It does not have direct connection
|
||||
to channel object in C-library for EPICS Channel Access.
|
||||
for creation just supply channel name to connect
|
||||
"""
|
||||
dbr_types=(
|
||||
DBR_NATIVE, # default type
|
||||
DBR_STRING, DBR_CHAR, DBR_FLOAT,
|
||||
DBR_SHORT, #/* same as DBR_INT */
|
||||
DBR_ENUM, DBR_LONG, DBR_DOUBLE,
|
||||
DBR_TIME_STRING, DBR_TIME_CHAR, DBR_TIME_FLOAT,
|
||||
DBR_TIME_SHORT, #:/* same as DBR_TIME_INT */
|
||||
DBR_TIME_ENUM, DBR_TIME_LONG, DBR_TIME_DOUBLE,
|
||||
DBR_CTRL_CHAR, DBR_CTRL_LONG,
|
||||
DBR_CTRL_ENUM, DBR_CTRL_DOUBLE
|
||||
)
|
||||
|
||||
def __init__(self, name, cb=None,noflush=None):
|
||||
if (not cb) : cb=self.update_info
|
||||
if name == "":
|
||||
raise ValueError(name)
|
||||
self.name=name
|
||||
self.field_type = None
|
||||
self.element_count = None
|
||||
self.puser = None
|
||||
self.conn_state = -1
|
||||
self.hostname = None
|
||||
self.raccess = None
|
||||
self.waccess = None
|
||||
self.sevr=None
|
||||
self.ts=None
|
||||
self.status=None
|
||||
self.evid=[]
|
||||
self.autoEvid=None
|
||||
self.__callbacks={}
|
||||
self.cbstate=None
|
||||
self.updated=False
|
||||
self.val=None
|
||||
self.chid=_ca.search(name,cb)
|
||||
if not noflush:
|
||||
self.flush()
|
||||
|
||||
def clear(self):
|
||||
if self.chid:
|
||||
self.clear_monitor()
|
||||
self.flush()
|
||||
_ca.clear(self.chid)
|
||||
self.flush()
|
||||
self.chid=None
|
||||
|
||||
def __del__(self):
|
||||
self.clear()
|
||||
|
||||
def wait_conn(self, wait=20, dt=0.05):
|
||||
n=0
|
||||
self.pend_event(dt)
|
||||
self.update_info()
|
||||
self.poll()
|
||||
while (not self.isConnected()):
|
||||
self.update_info()
|
||||
self.pend_event(dt)
|
||||
n=n+1
|
||||
if (n > wait ) :
|
||||
raise ECA_BADCHID("%s %d"%(self.name,n))
|
||||
return -1
|
||||
|
||||
def get(self,cb=None,Type=DBR_NATIVE, count=0, type=DBR_NATIVE, type_=DBR_NATIVE):
|
||||
try:
|
||||
if not self.isConnected():
|
||||
raise ECA_BADCHID(self.name)
|
||||
except:
|
||||
raise ECA_BADCHID(self.name)
|
||||
if (Type == DBR_NATIVE):
|
||||
if not(type == DBR_NATIVE):
|
||||
Type=type
|
||||
elif not(type_ == DBR_NATIVE):
|
||||
Type=type_
|
||||
rType=max(Type,type,type_)
|
||||
if rType not in self.dbr_types:
|
||||
raise TypeError(rType)
|
||||
if not cb: cb=self.update_val
|
||||
|
||||
self.cbstate=None
|
||||
self.updated=False
|
||||
try:
|
||||
_ca.get(self.chid, cb, Type, count)
|
||||
finally:
|
||||
pass
|
||||
|
||||
def put(self,*val,**kw):
|
||||
"""
|
||||
channel.put(valu) will put scalar value to channel. You may need to call channel.flush()
|
||||
|
||||
"""
|
||||
if( val == ()):
|
||||
print("No value(s) to put")
|
||||
else:
|
||||
if kw.has_key('cb'):
|
||||
cb=kw['cb']
|
||||
else:
|
||||
cb=None
|
||||
#self.__lock.acquire()
|
||||
try:
|
||||
_ca.put(self.chid, val, self.val, cb, DBR_NATIVE)
|
||||
finally:
|
||||
#self.__lock.release()
|
||||
pass
|
||||
|
||||
def put_and_notify(self,*val,**kw):
|
||||
if kw.has_key('cb'):
|
||||
cb=kw['cb']
|
||||
else:
|
||||
cb=None # ca_put_array_callback does not return value.
|
||||
if( val == ()):
|
||||
print("No value(s) to put")
|
||||
else:
|
||||
#self.__lock.acquire()
|
||||
try:
|
||||
_ca.put(self.chid,val,self.val,cb, DBR_NATIVE)
|
||||
finally:
|
||||
#self.__lock.release()
|
||||
pass
|
||||
|
||||
def monitor(self,callback=None,count=0,evmask=(DBE_VALUE|DBE_ALARM)):
|
||||
if(not callback):
|
||||
raise PyCa_NoCallback
|
||||
if (self.conn_state != 2):
|
||||
#print self.name,self.get_info()
|
||||
raise ECA_BADCHID(self.name)
|
||||
|
||||
self.update_info()
|
||||
if (self.field_type == DBR_NATIVE):
|
||||
#print self.name,self.get_info()
|
||||
raise ECA_BADTYPE(self.name)
|
||||
self.evid.append(_ca.monitor(self.chid,callback,count,evmask))
|
||||
self.__callbacks[self.evid[-1]]=callback
|
||||
return self.evid[-1]
|
||||
|
||||
def __clear_event(self,evid):
|
||||
if(_channel__debug): print("clearing evid:",evid)
|
||||
_ca.clear_monitor(evid)
|
||||
del self.__callbacks[evid]
|
||||
|
||||
def clear_monitor(self,evid=None):
|
||||
if(evid):
|
||||
if ( evid in self.evid):
|
||||
self.__clear_event(evid)
|
||||
i=self.evid.index(evid)
|
||||
del self.evid[i]
|
||||
i=self.evid.index(evid)
|
||||
del self.evid[i]
|
||||
else:
|
||||
for evid in self.evid:
|
||||
self.__clear_event(evid)
|
||||
self.evid=[]
|
||||
|
||||
def autoUpdate(self):
|
||||
if self.autoEvid == None:
|
||||
self.monitor(self.update_val)
|
||||
self.autoEvid=self.evid[-1]
|
||||
self.flush()
|
||||
|
||||
def clearAutoUpdate(self):
|
||||
if self.autoEvid is not None:
|
||||
self.clear_monitor(self.autoEvid)
|
||||
self.autoEvid=None
|
||||
self.flush()
|
||||
|
||||
def pendio(self,tmo=0.001):
|
||||
v=_ca.pendio(float(tmo))
|
||||
return v
|
||||
|
||||
def pend_io(self,tmo=0.001):
|
||||
v=_ca.pendio(float(tmo))
|
||||
return v
|
||||
|
||||
def pend_event(self,tmo=0.001):
|
||||
v=_ca.pend_event(float(tmo))
|
||||
return v
|
||||
|
||||
def poll(self):
|
||||
_ca.poll()
|
||||
|
||||
def flush(self,wait=0.001):
|
||||
v=_ca.flush(wait)
|
||||
return v
|
||||
|
||||
def update_val(self,valstat=None):
|
||||
if valstat ==None:
|
||||
raise caError("No value")
|
||||
#self.__lock.acquire()
|
||||
try:
|
||||
self.val=valstat[0]
|
||||
self.sevr=valstat[1]
|
||||
self.status=valstat[2]
|
||||
self.cbstate=1
|
||||
try:
|
||||
self.ts=valstat[3]
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
self.ctrl=valstat[4]
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
#self.__lock.release()
|
||||
self.updated=True
|
||||
pass
|
||||
|
||||
def clear_cbstate(self):
|
||||
#self.__lock.acquire()
|
||||
self.cbstate=None
|
||||
#self.__lock.release()
|
||||
|
||||
def state(self):
|
||||
self.get_info()
|
||||
return (self.conn_state - ch_state.cs_conn)
|
||||
|
||||
def isNeverConnected(self):
|
||||
self.get_info()
|
||||
return (self.conn_state == ch_state.cs_never_conn)
|
||||
|
||||
def isConnected(self):
|
||||
self.get_info()
|
||||
return (self.conn_state == ch_state.cs_conn)
|
||||
|
||||
def isPreviouslyConnected(self):
|
||||
self.get_info()
|
||||
return (self.conn_state == ch_state.cs_prev_conn)
|
||||
|
||||
def isDisonnected(self):
|
||||
self.get_info()
|
||||
return (self.conn_state == ch_state.cs_prev_conn)
|
||||
|
||||
def isClosed(self):
|
||||
self.get_info()
|
||||
return (self.conn_state == ch_state.cs_closed)
|
||||
|
||||
def get_info(self):
|
||||
"""
|
||||
update channel status information. return channel staus as a tuple.
|
||||
"""
|
||||
#self.__lock.acquire()
|
||||
try:
|
||||
info=(self.field_type, self.element_count, self.puser,
|
||||
self.conn_state, self.hostname, self.raccess,
|
||||
self.waccess) = _ca.ch_info(self.chid)
|
||||
finally:
|
||||
#self.__lock.release()
|
||||
pass
|
||||
return info
|
||||
|
||||
def update_info(self):
|
||||
"""
|
||||
Just update channel status information. No return value.
|
||||
"""
|
||||
self.get_info()
|
||||
|
||||
def fileno(self):
|
||||
"""returns socket number used to connect.Scoket id is shared by
|
||||
channels which are connected to the same IOC.
|
||||
It became obsolete in EPICS 3.14 version of Python-CA.
|
||||
You need to use fd_register function. But you may not need it anyway in multi-thread environment.
|
||||
"""
|
||||
return _ca.fileno(self.chid)
|
||||
|
||||
# convenient functions
|
||||
# you need to call Clear() function before stopping Python, otherwise it cause coredump. 2009/2/11 NY
|
||||
def __Ch(name,tmo=0.01):
|
||||
if (type(name) == type("")):
|
||||
if (__ca_dict.has_key(name)):
|
||||
ch=__ca_dict[name]
|
||||
else:
|
||||
try:
|
||||
ch=channel(name)
|
||||
ch.wait_conn()
|
||||
except:
|
||||
raise ECA_BADCHID(name)
|
||||
tmo=20*tmo
|
||||
__ca_dict_lock.acquire()
|
||||
try:
|
||||
__ca_dict[name]=ch
|
||||
finally:
|
||||
__ca_dict_lock.release()
|
||||
if( ch.state() != 0):
|
||||
ch.wait_conn(10)
|
||||
return ch
|
||||
else:
|
||||
raise ECA_BADTYPE(name)
|
||||
|
||||
def Info(name = "",tmo=0.01):
|
||||
"""
|
||||
returns a tuple as channel information.
|
||||
tuple format=(field_type, element_count, puser argument,
|
||||
connection_status, hostname:port,
|
||||
read access mode, write access mode)
|
||||
"""
|
||||
ch=__Ch(name,tmo=tmo)
|
||||
return ch.get_info()
|
||||
|
||||
def ClearAll():
|
||||
for name in __ca_dict.keys():
|
||||
Clear(name)
|
||||
|
||||
# __ca_dict should be cleared before Stopping Python
|
||||
atexit.register(ClearAll)
|
||||
|
||||
def Clear(name= ""):
|
||||
if (type(name) == type("")):
|
||||
__ca_dict_lock.acquire()
|
||||
try:
|
||||
if (__ca_dict.has_key(name)):
|
||||
ch=__ca_dict[name]
|
||||
del __ca_dict[name]
|
||||
ch.clear()
|
||||
del ch
|
||||
else:
|
||||
__ca_dict_lock.release()
|
||||
raise ECA_BADTYPE(name)
|
||||
finally:
|
||||
__ca_dict_lock.release()
|
||||
else:
|
||||
raise ECA_BADTYPE(name)
|
||||
|
||||
def Get(name="",count=0,Type=DBR_NATIVE,tmo=0.01,maxtmo=3):
|
||||
"""
|
||||
Get value from a channel "name".
|
||||
"""
|
||||
ch=__Ch(name,tmo)
|
||||
def CB(vals,ch=ch):
|
||||
ch.update_val(vals)
|
||||
ch.get(cb=CB,Type=Type,count=count)
|
||||
ch.flush()
|
||||
while not ch.updated:
|
||||
time.sleep(tmo)
|
||||
maxtmo -=tmo
|
||||
if maxtmo <=0:
|
||||
raise caError("No get response")
|
||||
return ch.val
|
||||
|
||||
def Put_and_Notify(name,val=None,cb=None):
|
||||
"""
|
||||
Convenient function:Put_and_Notify
|
||||
|
||||
calls put_and_notify with callback.
|
||||
If callback is None, then just put data to a channel.
|
||||
"""
|
||||
ch=__Ch(name,tmo=0.1)
|
||||
ch.put_and_notify(val,cb=cb)
|
||||
ch.flush()
|
||||
return ch.val
|
||||
|
||||
# define synonym
|
||||
Put=Put_and_Notify
|
||||
|
||||
def Put_and_Notify_Array(name,val,cb=None):
|
||||
"""
|
||||
put array test version : not tested with string arrays yet
|
||||
2007.8.30 T. Matsumoto
|
||||
"""
|
||||
ch=__Ch(name,tmo=0.1)
|
||||
apply(ch.put_and_notify,val,dict(cb=cb))
|
||||
ch.flush()
|
||||
return ch.val
|
||||
|
||||
# define synonym
|
||||
Put_Array=Put_and_Notify_Array
|
||||
|
||||
def Monitor(name,cb=None,evmask=(DBE_VALUE|DBE_ALARM)):
|
||||
ch=__Ch(name,tmo=0.1)
|
||||
if not cb:
|
||||
def myCB(val,ch=ch):
|
||||
print(ch.name,":",val[0],val[1],val[2],TS2Ascii(val[3]))
|
||||
else:
|
||||
def myCB(val, ch=ch, cb=cb):
|
||||
cb(ch,val)
|
||||
ch.clear_monitor()
|
||||
evid=ch.monitor(myCB,evmask=evmask)
|
||||
ch.flush()
|
||||
return evid
|
||||
|
||||
def ClearMonitor(name,evid=None):
|
||||
ch=__Ch(name,tmo=0.1)
|
||||
try:
|
||||
ch.clear_monitor(evid)
|
||||
return
|
||||
except:
|
||||
raise ECA_BADCHID(name)
|
||||
#
|
||||
def isIODone():
|
||||
if _ca.test_io()== 42:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
#
|
||||
# syncronus group class
|
||||
# Author: N.Yamamoto
|
||||
# Date: May 27.1999 (first version)
|
||||
#
|
||||
|
||||
class SyncGroup:
|
||||
def __init__(self):
|
||||
self.gid=_ca.sg_create()
|
||||
self.chs={}
|
||||
|
||||
def add(self, chs):
|
||||
try:
|
||||
for ch in chs:
|
||||
if(not self.chs.has_key(ch)):
|
||||
self.chs[ch]=0
|
||||
except:
|
||||
if(not self.chs.has_key(chs)):
|
||||
self.chs[chs]=0
|
||||
|
||||
def test(self):
|
||||
return _ca.sg_test(self.gid)
|
||||
|
||||
def reset(self):
|
||||
return _ca.sg_reset(self.gid)
|
||||
|
||||
def wait(self,tmo=1.0):
|
||||
return _ca.sg_block(self.gid,float(tmo))
|
||||
|
||||
def put(self,ch,*value,**kw):
|
||||
if kw.has_key("Type"):
|
||||
Type=kw["Type"]
|
||||
else:
|
||||
Type=DBR_NATIVE
|
||||
if self.chs.has_key(ch):
|
||||
self.chs[ch]=_ca.sg_put(self.gid, ch.chid,
|
||||
self.chs[ch], value , Type)
|
||||
|
||||
def get(self,ch):
|
||||
if self.chs.has_key(ch):
|
||||
self.chs[ch]=_ca.sg_get(self.gid,
|
||||
ch.chid, self.chs[ch])
|
||||
else:
|
||||
pass
|
||||
|
||||
def convert(self,ch):
|
||||
if self.chs.has_key(ch):
|
||||
val=_ca.ca_convert(ch.chid, self.chs[ch])
|
||||
ch.update_val(val[0])
|
||||
|
||||
def GetAll(self,tmo=1.0):
|
||||
for ch in self.chs.keys():
|
||||
self.chs[ch]=_ca.sg_get(self.gid,
|
||||
ch.chid, self.chs[ch])
|
||||
st=_ca.sg_block(self.gid,tmo)
|
||||
if st == 0:
|
||||
for ch in self.chs.keys():
|
||||
val=_ca.ca_convert(ch.chid,self.chs[ch])
|
||||
ch.update_val(val[0])
|
||||
else:
|
||||
raise Exception("CA_SG time out")
|
||||
|
||||
# TimeStamp utilities
|
||||
# time.gmtime(631152000.0)=(1990, 1, 1, 0, 0, 0, 0, 1, 0)
|
||||
#
|
||||
__EPICS_TS_EPOCH=631152000.0
|
||||
|
||||
def TS2Ascii(ts):
|
||||
import math
|
||||
tstr=time.ctime(ts+__EPICS_TS_EPOCH)
|
||||
nsstr=".%03d"%(math.modf(ts + __EPICS_TS_EPOCH)[0]*1000)
|
||||
return tstr[:-5]+nsstr+tstr[-5:]
|
||||
|
||||
def TS2time(ts):
|
||||
return time.localtime(ts+__EPICS_TS_EPOCH)
|
||||
|
||||
def TS2UTC(ts):
|
||||
return (ts+__EPICS_TS_EPOCH)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python
|
||||
## @package ca: EPICS-CA interface module for Python.
|
||||
"""CA modlue : EPICS-CA interface module for Python.
|
||||
This module provide a version of EPICS-CA and Python interface.
|
||||
It users C module _ca. _ca module basically maps C-API in EPICS ca library into python. Interface between ca.py and _ca module is subject for change. You should not depend on it. API in ca.py will be preserved in future releases as much as possible.
|
||||
Author: Noboru Yamamoto, KEK, JAPAN. -2007.
|
||||
$Revision: 1.4 $
|
||||
"""
|
||||
|
||||
__version__ = "$Revision: 1.4 $"
|
||||
# $Source: /cvs/G/EPICS/extensions/src/PythonCA/src/ca.py,v $
|
||||
|
||||
import time,gc,sys,atexit
|
||||
if sys.hexversion >= 0x03000000:
|
||||
import _thread as thread
|
||||
else:
|
||||
import thread
|
||||
|
||||
# autGIL is not compatible with Tkinter and wx. So code was removed
|
||||
|
||||
# force thread module to call PyEval_InitThread in it.
|
||||
__foo_lock=thread.allocate_lock()
|
||||
def __foo():
|
||||
"""
|
||||
test function foo
|
||||
|
||||
This function is used to ensure thread module is initialized before
|
||||
loading _ca module.
|
||||
"""
|
||||
global __foo_lock
|
||||
__foo_lock.release()
|
||||
thread.exit_thread()
|
||||
|
||||
# See Python/Include/ceval.h
|
||||
__foo_lock.acquire()
|
||||
thread.start_new_thread(__foo,()) # __foo release lock
|
||||
__foo_lock.acquire() # make sure threading is activated
|
||||
|
||||
import _ca
|
||||
# version from _ca314.cpp
|
||||
version=_ca.version
|
||||
revision=_ca.release
|
||||
|
||||
# some constants for EPICS channel Access library
|
||||
from cadefs import *
|
||||
from caError import *
|
||||
|
||||
# for FNAL version you need to provide _ca_fnal.py and import every thin from them
|
||||
from _ca_fnal import *
|
||||
@@ -0,0 +1,461 @@
|
||||
import sys
|
||||
if sys.hexversion >= 0x03000000:
|
||||
intern = sys.intern
|
||||
|
||||
from ca import _ca
|
||||
class caError(_ca.error):
|
||||
""" EPICS ca.py Errors"""
|
||||
pass
|
||||
|
||||
__caErrorMsg=(
|
||||
"Normal successful completion",
|
||||
"Maximum simultaneous IOC connections exceeded",
|
||||
"Unknown internet host",
|
||||
"Unknown internet service",
|
||||
"Unable to allocate a new socket",
|
||||
"Unable to connect to internet host or service",
|
||||
"Unable to allocate additional dynamic memory",
|
||||
"Unknown IO channel",
|
||||
"Record field specified inappropriate for channel specified",
|
||||
"The requested data transfer is greater than available memory or EPICS_CA_MAX_ARRAY_BYTES",
|
||||
"User specified timeout on IO operation expired",
|
||||
"Sorry, that feature is planned but not supported at this time",
|
||||
"The supplied string is unusually large",
|
||||
"The request was ignored because the specified channel is disconnected",
|
||||
"The data type specifed is invalid",
|
||||
"Remote Channel not found",
|
||||
"Unable to locate all user specified channels",
|
||||
"Channel Access Internal Failure",
|
||||
"The requested local DB operation failed",
|
||||
"Channel read request failed",
|
||||
"Channel write request failed",
|
||||
"Channel subscription request failed",
|
||||
"Invalid element count requested",
|
||||
"Invalid string",
|
||||
"Virtual circuit disconnect",
|
||||
"Identical process variable names on multiple servers",
|
||||
"Request inappropriate within subscription (monitor) update callback",
|
||||
"Database value get for that channel failed during channel search",
|
||||
"Unable to initialize without the vxWorks VX_FP_TASK task option set",
|
||||
"Event queue overflow has prevented first pass event after event add",
|
||||
"Bad event subscription (monitor) identifier",
|
||||
"Remote channel has new network address",
|
||||
"New or resumed network connection",
|
||||
"Specified task isnt a member of a CA context",
|
||||
"Attempt to use defunct CA feature failed",
|
||||
"The supplied string is empty",
|
||||
"Unable to spawn the CA repeater thread- auto reconnect will fail",
|
||||
"No channel id match for search reply- search reply ignored",
|
||||
"Reseting dead connection- will try to reconnect",
|
||||
"Server (IOC) has fallen behind or is not responding- still waiting",
|
||||
"No internet interface with broadcast available",
|
||||
"Invalid event selection mask",
|
||||
"IO operations have completed",
|
||||
"IO operations are in progress",
|
||||
"Invalid synchronous group identifier",
|
||||
"Put callback timed out",
|
||||
"Read access denied",
|
||||
"Write access denied",
|
||||
"Requested feature is no longer supported",
|
||||
"Empty PV search address list",
|
||||
"No reasonable data conversion between client and server types",
|
||||
"Invalid channel identifier",
|
||||
"Invalid function pointer",
|
||||
"Thread is already attached to a client context",
|
||||
"Not supported by attached service",
|
||||
"User destroyed channel",
|
||||
"Invalid channel priority",
|
||||
"Preemptive callback not enabled - additional threads may not join context",
|
||||
"Client's protocol revision does not support transfers exceeding 16k bytes",
|
||||
"Virtual circuit connection sequence aborted",
|
||||
"Virtual circuit unresponsive",
|
||||
)
|
||||
_caErrorMsg=map(intern,__caErrorMsg)
|
||||
if sys.hexversion >= 0x03000000:
|
||||
_caErrorMsg = list(_caErrorMsg)
|
||||
|
||||
ErrCode2Class={}
|
||||
class PyCa_NoCallback(caError):
|
||||
__doc__="Null callback routine"
|
||||
CA_M_MSG_NO = 0x0000FFF8
|
||||
CA_M_SEVERITY = 0x00000007
|
||||
CA_M_LEVEL = 0x00000003
|
||||
CA_M_SUCCESS = 0x00000001
|
||||
CA_M_ERROR = 0x00000002
|
||||
CA_M_SEVERE = 0x00000004
|
||||
CA_S_MSG_NO= 0x0D
|
||||
CA_S_SEVERITY=0x03
|
||||
CA_V_MSG_NO= 0x03
|
||||
CA_V_SEVERITY= 0x00
|
||||
CA_V_SUCCESS= 0x00
|
||||
|
||||
def CA_EXTRACT_MSG_NO(code): return ( ( (code) & CA_M_MSG_NO ) >> CA_V_MSG_NO )
|
||||
def CA_EXTRACT_SEVERITY(code): return ( ( (code) & CA_M_SEVERITY ) >> CA_V_SEVERITY)
|
||||
def CA_EXTRACT_SUCCESS(code): ( ( (code) & CA_M_SUCCESS ) >> CA_V_SUCCESS )
|
||||
|
||||
class ECA_NORMAL(caError):
|
||||
__doc__=_caErrorMsg[0]
|
||||
__errcode__=1
|
||||
|
||||
ErrCode2Class[1]=ECA_NORMAL
|
||||
|
||||
class ECA_MAXIOC(caError):
|
||||
__doc__=_caErrorMsg[1]
|
||||
__errcode__=10
|
||||
|
||||
ErrCode2Class[10]=ECA_MAXIOC
|
||||
|
||||
class ECA_UKNHOST(caError):
|
||||
__doc__=_caErrorMsg[2]
|
||||
__errcode__=18
|
||||
|
||||
ErrCode2Class[18]=ECA_UKNHOST
|
||||
|
||||
class ECA_UKNSERV(caError):
|
||||
__doc__=_caErrorMsg[3]
|
||||
__errcode__=26
|
||||
|
||||
ErrCode2Class[26]=ECA_UKNSERV
|
||||
|
||||
class ECA_SOCK(caError):
|
||||
__doc__=_caErrorMsg[4]
|
||||
__errcode__=34
|
||||
|
||||
ErrCode2Class[34]=ECA_SOCK
|
||||
|
||||
class ECA_CONN(caError):
|
||||
__doc__=_caErrorMsg[5]
|
||||
__errcode__=40
|
||||
|
||||
ErrCode2Class[40]=ECA_CONN
|
||||
|
||||
class ECA_ALLOCMEM(caError):
|
||||
__doc__=_caErrorMsg[6]
|
||||
__errcode__=48
|
||||
|
||||
ErrCode2Class[48]=ECA_ALLOCMEM
|
||||
|
||||
class ECA_UKNCHAN(caError):
|
||||
__doc__=_caErrorMsg[7]
|
||||
__errcode__=56
|
||||
|
||||
ErrCode2Class[56]=ECA_UKNCHAN
|
||||
|
||||
class ECA_UKNFIELD(caError):
|
||||
__doc__=_caErrorMsg[8]
|
||||
__errcode__=64
|
||||
|
||||
ErrCode2Class[64]=ECA_UKNFIELD
|
||||
|
||||
class ECA_TOLARGE(caError):
|
||||
__doc__=_caErrorMsg[9]
|
||||
__errcode__=72
|
||||
|
||||
ErrCode2Class[72]=ECA_TOLARGE
|
||||
|
||||
class ECA_TIMEOUT(caError):
|
||||
__doc__=_caErrorMsg[10]
|
||||
__errcode__=80
|
||||
|
||||
ErrCode2Class[80]=ECA_TIMEOUT
|
||||
|
||||
class ECA_NOSUPPORT(caError):
|
||||
__doc__=_caErrorMsg[11]
|
||||
__errcode__=88
|
||||
|
||||
ErrCode2Class[88]=ECA_NOSUPPORT
|
||||
|
||||
class ECA_STRTOBIG(caError):
|
||||
__doc__=_caErrorMsg[12]
|
||||
__errcode__=96
|
||||
|
||||
ErrCode2Class[96]=ECA_STRTOBIG
|
||||
|
||||
class ECA_DISCONNCHID(caError):
|
||||
__doc__=_caErrorMsg[13]
|
||||
__errcode__=106
|
||||
|
||||
ErrCode2Class[106]=ECA_DISCONNCHID
|
||||
|
||||
class ECA_BADTYPE(caError):
|
||||
__doc__=_caErrorMsg[14]
|
||||
__errcode__=114
|
||||
|
||||
ErrCode2Class[114]=ECA_BADTYPE
|
||||
|
||||
class ECA_CHIDNOTFND(caError):
|
||||
__doc__=_caErrorMsg[15]
|
||||
__errcode__=123
|
||||
|
||||
ErrCode2Class[123]=ECA_CHIDNOTFND
|
||||
|
||||
class ECA_CHIDRETRY(caError):
|
||||
__doc__=_caErrorMsg[16]
|
||||
__errcode__=131
|
||||
|
||||
ErrCode2Class[131]=ECA_CHIDRETRY
|
||||
|
||||
class ECA_INTERNAL(caError):
|
||||
__doc__=_caErrorMsg[17]
|
||||
__errcode__=142
|
||||
|
||||
ErrCode2Class[142]=ECA_INTERNAL
|
||||
|
||||
class ECA_DBLCLFAIL(caError):
|
||||
__doc__=_caErrorMsg[18]
|
||||
__errcode__=144
|
||||
|
||||
ErrCode2Class[144]=ECA_DBLCLFAIL
|
||||
|
||||
class ECA_GETFAIL(caError):
|
||||
__doc__=_caErrorMsg[19]
|
||||
__errcode__=152
|
||||
|
||||
ErrCode2Class[152]=ECA_GETFAIL
|
||||
|
||||
class ECA_PUTFAIL(caError):
|
||||
__doc__=_caErrorMsg[20]
|
||||
__errcode__=160
|
||||
|
||||
ErrCode2Class[160]=ECA_PUTFAIL
|
||||
|
||||
class ECA_ADDFAIL(caError):
|
||||
__doc__=_caErrorMsg[21]
|
||||
__errcode__=168
|
||||
|
||||
ErrCode2Class[168]=ECA_ADDFAIL
|
||||
|
||||
class ECA_BADCOUNT(caError):
|
||||
__doc__=_caErrorMsg[22]
|
||||
__errcode__=176
|
||||
|
||||
ErrCode2Class[176]=ECA_BADCOUNT
|
||||
|
||||
class ECA_BADSTR(caError):
|
||||
__doc__=_caErrorMsg[23]
|
||||
__errcode__=186
|
||||
|
||||
ErrCode2Class[186]=ECA_BADSTR
|
||||
|
||||
class ECA_DISCONN(caError):
|
||||
__doc__=_caErrorMsg[24]
|
||||
__errcode__=192
|
||||
|
||||
ErrCode2Class[192]=ECA_DISCONN
|
||||
|
||||
class ECA_DBLCHNL(caError):
|
||||
__doc__=_caErrorMsg[25]
|
||||
__errcode__=200
|
||||
|
||||
ErrCode2Class[200]=ECA_DBLCHNL
|
||||
|
||||
class ECA_EVDISALLOW(caError):
|
||||
__doc__=_caErrorMsg[26]
|
||||
__errcode__=210
|
||||
|
||||
ErrCode2Class[210]=ECA_EVDISALLOW
|
||||
|
||||
class ECA_BUILDGET(caError):
|
||||
__doc__=_caErrorMsg[27]
|
||||
__errcode__=216
|
||||
|
||||
ErrCode2Class[216]=ECA_BUILDGET
|
||||
|
||||
class ECA_NEEDSFP(caError):
|
||||
__doc__=_caErrorMsg[28]
|
||||
__errcode__=224
|
||||
|
||||
ErrCode2Class[224]=ECA_NEEDSFP
|
||||
|
||||
class ECA_OVEVFAIL(caError):
|
||||
__doc__=_caErrorMsg[29]
|
||||
__errcode__=232
|
||||
|
||||
ErrCode2Class[232]=ECA_OVEVFAIL
|
||||
|
||||
class ECA_BADMONID(caError):
|
||||
__doc__=_caErrorMsg[30]
|
||||
__errcode__=242
|
||||
|
||||
ErrCode2Class[242]=ECA_BADMONID
|
||||
|
||||
class ECA_NEWADDR(caError):
|
||||
__doc__=_caErrorMsg[31]
|
||||
__errcode__=248
|
||||
|
||||
ErrCode2Class[248]=ECA_NEWADDR
|
||||
|
||||
class ECA_NEWCONN(caError):
|
||||
__doc__=_caErrorMsg[32]
|
||||
__errcode__=259
|
||||
|
||||
ErrCode2Class[259]=ECA_NEWCONN
|
||||
|
||||
class ECA_NOCACTX(caError):
|
||||
__doc__=_caErrorMsg[33]
|
||||
__errcode__=264
|
||||
|
||||
ErrCode2Class[264]=ECA_NOCACTX
|
||||
|
||||
class ECA_DEFUNCT(caError):
|
||||
__doc__=_caErrorMsg[34]
|
||||
__errcode__=278
|
||||
|
||||
ErrCode2Class[278]=ECA_DEFUNCT
|
||||
|
||||
class ECA_EMPTYSTR(caError):
|
||||
__doc__=_caErrorMsg[35]
|
||||
__errcode__=280
|
||||
|
||||
ErrCode2Class[280]=ECA_EMPTYSTR
|
||||
|
||||
class ECA_NOREPEATER(caError):
|
||||
__doc__=_caErrorMsg[36]
|
||||
__errcode__=288
|
||||
|
||||
ErrCode2Class[288]=ECA_NOREPEATER
|
||||
|
||||
class ECA_NOCHANMSG(caError):
|
||||
__doc__=_caErrorMsg[37]
|
||||
__errcode__=296
|
||||
|
||||
ErrCode2Class[296]=ECA_NOCHANMSG
|
||||
|
||||
class ECA_DLCKREST(caError):
|
||||
__doc__=_caErrorMsg[38]
|
||||
__errcode__=304
|
||||
|
||||
ErrCode2Class[304]=ECA_DLCKREST
|
||||
|
||||
class ECA_SERVBEHIND(caError):
|
||||
__doc__=_caErrorMsg[39]
|
||||
__errcode__=312
|
||||
|
||||
ErrCode2Class[312]=ECA_SERVBEHIND
|
||||
|
||||
class ECA_NOCAST(caError):
|
||||
__doc__=_caErrorMsg[40]
|
||||
__errcode__=320
|
||||
|
||||
ErrCode2Class[320]=ECA_NOCAST
|
||||
|
||||
class ECA_BADMASK(caError):
|
||||
__doc__=_caErrorMsg[41]
|
||||
__errcode__=330
|
||||
|
||||
ErrCode2Class[330]=ECA_BADMASK
|
||||
|
||||
class ECA_IODONE(caError):
|
||||
__doc__=_caErrorMsg[42]
|
||||
__errcode__=339
|
||||
|
||||
ErrCode2Class[339]=ECA_IODONE
|
||||
|
||||
class ECA_IOINPROGRESS(caError):
|
||||
__doc__=_caErrorMsg[43]
|
||||
__errcode__=347
|
||||
|
||||
ErrCode2Class[347]=ECA_IOINPROGRESS
|
||||
|
||||
class ECA_BADSYNCGRP(caError):
|
||||
__doc__=_caErrorMsg[44]
|
||||
__errcode__=354
|
||||
|
||||
ErrCode2Class[354]=ECA_BADSYNCGRP
|
||||
|
||||
class ECA_PUTCBINPROG(caError):
|
||||
__doc__=_caErrorMsg[45]
|
||||
__errcode__=362
|
||||
|
||||
ErrCode2Class[362]=ECA_PUTCBINPROG
|
||||
|
||||
class ECA_NORDACCESS(caError):
|
||||
__doc__=_caErrorMsg[46]
|
||||
__errcode__=368
|
||||
|
||||
ErrCode2Class[368]=ECA_NORDACCESS
|
||||
|
||||
class ECA_NOWTACCESS(caError):
|
||||
__doc__=_caErrorMsg[47]
|
||||
__errcode__=376
|
||||
|
||||
ErrCode2Class[376]=ECA_NOWTACCESS
|
||||
|
||||
class ECA_ANACHRONISM(caError):
|
||||
__doc__=_caErrorMsg[48]
|
||||
__errcode__=386
|
||||
|
||||
ErrCode2Class[386]=ECA_ANACHRONISM
|
||||
|
||||
class ECA_NOSEARCHADDR(caError):
|
||||
__doc__=_caErrorMsg[49]
|
||||
__errcode__=392
|
||||
|
||||
ErrCode2Class[392]=ECA_NOSEARCHADDR
|
||||
|
||||
class ECA_NOCONVERT(caError):
|
||||
__doc__=_caErrorMsg[50]
|
||||
__errcode__=400
|
||||
|
||||
ErrCode2Class[400]=ECA_NOCONVERT
|
||||
|
||||
class ECA_BADCHID(caError):
|
||||
__doc__=_caErrorMsg[51]
|
||||
__errcode__=410
|
||||
|
||||
ErrCode2Class[410]=ECA_BADCHID
|
||||
|
||||
class ECA_BADFUNCPTR(caError):
|
||||
__doc__=_caErrorMsg[52]
|
||||
__errcode__=418
|
||||
|
||||
ErrCode2Class[418]=ECA_BADFUNCPTR
|
||||
|
||||
class ECA_ISATTACHED(caError):
|
||||
__doc__=_caErrorMsg[53]
|
||||
__errcode__=424
|
||||
|
||||
ErrCode2Class[424]=ECA_ISATTACHED
|
||||
|
||||
class ECA_UNAVAILINSERV(caError):
|
||||
__doc__=_caErrorMsg[54]
|
||||
__errcode__=432
|
||||
|
||||
ErrCode2Class[432]=ECA_UNAVAILINSERV
|
||||
|
||||
class ECA_CHANDESTROY(caError):
|
||||
__doc__=_caErrorMsg[55]
|
||||
__errcode__=440
|
||||
|
||||
ErrCode2Class[440]=ECA_CHANDESTROY
|
||||
|
||||
class ECA_BADPRIORITY(caError):
|
||||
__doc__=_caErrorMsg[56]
|
||||
__errcode__=450
|
||||
|
||||
ErrCode2Class[450]=ECA_BADPRIORITY
|
||||
|
||||
class ECA_NOTTHREADED(caError):
|
||||
__doc__=_caErrorMsg[57]
|
||||
__errcode__=458
|
||||
|
||||
ErrCode2Class[458]=ECA_NOTTHREADED
|
||||
|
||||
class ECA_16KARRAYCLIENT(caError):
|
||||
__doc__=_caErrorMsg[58]
|
||||
__errcode__=464
|
||||
|
||||
ErrCode2Class[464]=ECA_16KARRAYCLIENT
|
||||
|
||||
class ECA_CONNSEQTMO(caError):
|
||||
__doc__=_caErrorMsg[59]
|
||||
__errcode__=472
|
||||
|
||||
ErrCode2Class[472]=ECA_CONNSEQTMO
|
||||
|
||||
class ECA_UNRESPTMO(caError):
|
||||
__doc__=_caErrorMsg[60]
|
||||
__errcode__=480
|
||||
|
||||
ErrCode2Class[480]=ECA_UNRESPTMO
|
||||
|
||||
@@ -0,0 +1,647 @@
|
||||
# ca_util.py - a thin wrapper around CaChannel
|
||||
# Tim Mooney 12/05/2008
|
||||
#
|
||||
# Modified by Xiaoqiang Wang to be Python 3 compatible.
|
||||
|
||||
"""ca_util.py is a wrapper around CaChannel that allows the caller to write,
|
||||
e.g.,
|
||||
caget("xxx:m1")
|
||||
instead of having to write
|
||||
m1 = CaChannel()
|
||||
m1.searchw("xxx:m1")
|
||||
m1.getw()
|
||||
Also, ca_util defends against null PV names and some effects of short-term
|
||||
CA disconnections, and it can verify that caput*() operations succeeded.
|
||||
"""
|
||||
|
||||
version = "2.0"
|
||||
|
||||
import ca
|
||||
import CaChannel
|
||||
import time
|
||||
import sys
|
||||
|
||||
# DBR types
|
||||
# ca.DBR_STRING = 0
|
||||
# ca.DBR_SHORT = 1
|
||||
# ca.DBR_INT = 1
|
||||
# ca.DBR_FLOAT = 2
|
||||
# ca.DBR_ENUM = 3
|
||||
# ca.DBR_CHAR = 4
|
||||
# ca.DBR_LONG = 5
|
||||
# ca.DBR_DOUBLE = 6
|
||||
|
||||
# If caller imported CaChannel using "from CaChannel import *", then the
|
||||
# class CaChannel will have the same name as the module CaChannel, and
|
||||
# we won't be able to see the module attribute, 'CaChannel.__file__'.
|
||||
def getCaChannelFileName():
|
||||
""" For internal ca_util use"""
|
||||
return CaChannel.__file__
|
||||
|
||||
#######################################################################
|
||||
# Human readable exception description
|
||||
# try:
|
||||
# x = x + 1
|
||||
# except:
|
||||
# print formatExceptionInfo()
|
||||
import sys
|
||||
import traceback
|
||||
def formatExceptionInfo(maxTBlevel=5):
|
||||
cla, exc, trbk = sys.exc_info()
|
||||
excName = cla.__name__
|
||||
try:
|
||||
excArgs = exc.__dict__["args"]
|
||||
except KeyError:
|
||||
excArgs = "<no args>"
|
||||
excTb = traceback.format_tb(trbk, maxTBlevel)
|
||||
return (excName, excArgs, excTb)
|
||||
|
||||
#######################################################################
|
||||
# channel-access connection states
|
||||
ca_states = {}
|
||||
# ...from cadef.h:
|
||||
ca_states[ca.cs_never_conn] = "never connected"
|
||||
ca_states[ca.cs_prev_conn] = "previously connected"
|
||||
ca_states[ca.cs_conn] = "connected"
|
||||
ca_states[ca.cs_closed] = "closed"
|
||||
ca_states[ca.cs_never_search] = "never searched"
|
||||
|
||||
|
||||
|
||||
#######################################################################
|
||||
# default settings for ca_util
|
||||
defaultTimeout = None # 'None' means use CaChannel's timeout
|
||||
defaultRetries = 3
|
||||
readCheckTolerance = None # 'None" means don't check
|
||||
|
||||
def set_ca_util_defaults(timeout=None, retries=None, read_check_tolerance=None):
|
||||
"""
|
||||
usage: old = set_ca_util_defaults(timeout=None, retries=None,
|
||||
read_check_tolerance=None)
|
||||
alternate: set_ca_util_defaults(defaultsList), where defaultsList is like
|
||||
the list returned by get_ca_util_defaults()
|
||||
Setting an argument to the string "NONE" disables it.
|
||||
Returns the list of previous default values:
|
||||
[defaultTimeout, defaultRetries, readCheckTolerance]
|
||||
"""
|
||||
global defaultTimeout, defaultRetries, readCheckTolerance
|
||||
old = [defaultTimeout, defaultRetries, readCheckTolerance]
|
||||
if type(timeout) == type([]):
|
||||
argList = timeout
|
||||
timeout = argList[0]
|
||||
retries = argList[1]
|
||||
read_check_tolerance = argList[2]
|
||||
if (timeout!=None) : defaultTimeout = timeout
|
||||
if (retries!=None) : defaultRetries = retries
|
||||
if (read_check_tolerance!=None) : readCheckTolerance = read_check_tolerance
|
||||
return old
|
||||
|
||||
def get_ca_util_defaults():
|
||||
"""
|
||||
usage: myList = get_ca_util_defaults()
|
||||
myList is set to [defaultTimeout, defaultRetries, readCheckTolerance]
|
||||
"""
|
||||
global defaultTimeout, defaultRetries, readCheckTolerance
|
||||
return [defaultTimeout, defaultRetries, readCheckTolerance]
|
||||
|
||||
def set_ca_util_default_timeout(timeout=None):
|
||||
"""
|
||||
usage: old = set_ca_util_default_timeout(timeout=None)
|
||||
If timeout == "NONE", then ca_util doesn't specify any timeout in
|
||||
calls to underlying software.
|
||||
Returns previous default timeout.
|
||||
"""
|
||||
global defaultTimeout
|
||||
old = defaultTimeout
|
||||
defaultTimeout = timeout
|
||||
return old
|
||||
|
||||
def get_ca_util_default_timeout():
|
||||
global defaultTimeout
|
||||
return defaultTimeout
|
||||
|
||||
def set_ca_util_default_retries(retries=None):
|
||||
"""
|
||||
usage: old = set_ca_util_default_retries(retries=None)
|
||||
If retries == "NONE", then ca_util doesn't do any retries.
|
||||
Returns previous default retries.
|
||||
"""
|
||||
global defaultRetries
|
||||
old = defaultRetries
|
||||
defaultRetries = retries
|
||||
return old
|
||||
|
||||
def get_ca_util_default_retries():
|
||||
global defaultRetries
|
||||
return defaultRetries
|
||||
|
||||
def set_ca_util_default_read_check_tolerance(read_check_tolerance=None):
|
||||
"""
|
||||
usage: old = set_ca_util_default_read_check_tolerance(read_check_tolerance=None)
|
||||
If read_check_tolerance == "NONE", then ca_util doesn't compare the value
|
||||
it reads to the value it wrote.
|
||||
Returns previous default tolerance.
|
||||
"""
|
||||
global readCheckTolerance
|
||||
old = readCheckTolerance
|
||||
readCheckTolerance = read_check_tolerance
|
||||
return old
|
||||
|
||||
def get_ca_util_default_read_check_tolerance():
|
||||
global readCheckTolerance
|
||||
return readCheckTolerance
|
||||
|
||||
|
||||
#######################################################################
|
||||
# The dictionary, cadict, will be used to associate PV names with the
|
||||
# machinery required to talk to EPICS PV's. If no entry is found (the
|
||||
# name hasn't been used yet in a ca call), then we create a new instance
|
||||
# of CaChannel, connect it to the PV, and put it in the dictionary. We also
|
||||
# include a flag some of the ca_util routines can use to check if a callback
|
||||
# has occurred for this PV.
|
||||
|
||||
class cadictEntry:
|
||||
def __init__(self, channel):
|
||||
self.channel = channel
|
||||
self.callbackReceived = 0 # reserved for use by caputw()
|
||||
self.field_type = channel.field_type()
|
||||
self.element_count = channel.element_count()
|
||||
#self.host_name = channel.host_name()
|
||||
|
||||
cadict = {}
|
||||
|
||||
#######################################################################
|
||||
ca_utilExceptionStrings = ["No name was provided.", "Readback disagrees with put value.",
|
||||
"PV is not connected."]
|
||||
EXCEPTION_NULL_NAME = 0
|
||||
EXCEPTION_READBACK_DISAGREES = 1
|
||||
EXCEPTION_NOT_CONNECTED = 2
|
||||
|
||||
class ca_utilException(Exception):
|
||||
def __init__(self, *args):
|
||||
Exception.__init__(self, *args)
|
||||
self.errorNumber = args[0]
|
||||
|
||||
def __int__(self):
|
||||
return int(self.errorNumber)
|
||||
|
||||
def __str__(self):
|
||||
return ca_utilExceptionStrings[self.errorNumber]
|
||||
|
||||
|
||||
#######################################################################
|
||||
def convertToType(type, value):
|
||||
if type == ca.DBR_STRING:
|
||||
return str(value)
|
||||
elif type == ca.DBR_SHORT or type == ca.DBR_INT or type == ca.DBR_LONG:
|
||||
try:
|
||||
n = int(value)
|
||||
except:
|
||||
n = 0
|
||||
return n
|
||||
elif type == ca.DBR_FLOAT or type == ca.DBR_DOUBLE:
|
||||
try:
|
||||
n = float(value)
|
||||
except:
|
||||
n = 0.0
|
||||
return n
|
||||
elif type == ca.DBR_ENUM:
|
||||
return value
|
||||
elif type == ca.DBR_CHAR:
|
||||
return value
|
||||
else:
|
||||
return value
|
||||
|
||||
#######################################################################
|
||||
def checkName(name, timeout=None, retries=None):
|
||||
"""
|
||||
usage: checkName("xxx:m1.VAL", timeout=None, retries=None)
|
||||
Intended for internal use by ca_util functions.
|
||||
"""
|
||||
|
||||
global cadict, defaultTimeout, defaultRetries
|
||||
if not name:
|
||||
raise ca_utilException(EXCEPTION_NULL_NAME)
|
||||
|
||||
if ((timeout == None) and (defaultTimeout != None)): timeout = defaultTimeout
|
||||
if (timeout == "NONE"): timeout = None
|
||||
|
||||
if ((retries == None) and (defaultRetries != None)): retries = defaultRetries
|
||||
if ((retries == None) or (retries == "NONE")): retries = 0
|
||||
|
||||
tries = 0
|
||||
while (name not in cadict) and (tries <= retries):
|
||||
# Make a new entry in the PV-name dictionary
|
||||
try:
|
||||
channel = CaChannel.CaChannel()
|
||||
if (timeout != None): channel.setTimeout(timeout)
|
||||
channel.searchw(name)
|
||||
cadict[name] = cadictEntry(channel)
|
||||
except CaChannel.CaChannelException:
|
||||
status = sys.exc_info()[1]
|
||||
del channel
|
||||
tries += 1
|
||||
|
||||
if (name not in cadict):
|
||||
print("ca_util.checkName: Can't connect to '%s'" % name)
|
||||
raise CaChannel.CaChannelException(status)
|
||||
|
||||
#######################################################################
|
||||
def castate(name=None, timeout=None, retries=None):
|
||||
"""usage: val = castate("xxx:m1.VAL", timeout=None, retries=None)
|
||||
Try to read a PV, to find out whether it's really connected, and
|
||||
whether caller is permitted to read and write it, without allowing
|
||||
any exceptions to be thrown at the caller.
|
||||
"""
|
||||
|
||||
global cadict, defaultTimeout, defaultRetries
|
||||
|
||||
if not name: return "Null name has no state"
|
||||
|
||||
# The only reliable way to check the *current* state of a PV is to attempt to use it.
|
||||
try:
|
||||
val = caget(name, timeout=timeout, retries=retries)
|
||||
except CaChannel.CaChannelException:
|
||||
pass
|
||||
|
||||
try:
|
||||
checkName(name, timeout=timeout)
|
||||
except CaChannel.CaChannelException:
|
||||
return "not connected"
|
||||
except:
|
||||
return "error"
|
||||
|
||||
try:
|
||||
state = cadict[name].channel.state()
|
||||
except CaChannel.CaChannelException:
|
||||
return "not connected"
|
||||
except:
|
||||
return "error"
|
||||
else:
|
||||
try:
|
||||
read_access = cadict[name].channel.read_access()
|
||||
write_access = cadict[name].channel.write_access()
|
||||
if state in ca_states:
|
||||
s = ca_states[state]
|
||||
else:
|
||||
s = "unknown state"
|
||||
if not read_access: s += ", noread"
|
||||
if not write_access: s += ", nowrite"
|
||||
return s
|
||||
except:
|
||||
return "error"
|
||||
|
||||
#######################################################################
|
||||
def caget(name, timeout=None, retries=None, req_type=None, req_count=None):
|
||||
"""usage: val = caget("xxx:m1.VAL", timeout=None, retries=None,
|
||||
req_type=None, req_count=None)"""
|
||||
|
||||
global cadict, defaultTimeout, defaultRetries
|
||||
|
||||
if not name:
|
||||
print("caget: no PV name supplied")
|
||||
raise ca_utilException(EXCEPTION_NULL_NAME)
|
||||
if ((timeout==None) and (defaultTimeout != None)): timeout = defaultTimeout
|
||||
if (timeout == "NONE"): timeout = None
|
||||
if ((retries==None) and (defaultRetries != None)): retries = defaultRetries
|
||||
if ((retries == None) or (retries == "NONE")): retries = 0
|
||||
retries = max(retries,0)
|
||||
retry = retries + 1
|
||||
success = 0
|
||||
|
||||
# CaChannel sometimes chokes when it tries to process a channel that has been disconnected.
|
||||
# The simplest fix is to clear the channel and reconnect to the PV, which we can do cleanly
|
||||
# by deleting our dict entry for the channel, and calling checkName() to make a new entry.
|
||||
|
||||
while ((not success) and (retry > 0)):
|
||||
checked = 0
|
||||
while ((not checked) and (retry > 0)):
|
||||
retry -= 1
|
||||
try:
|
||||
checkName(name, timeout=timeout)
|
||||
except CaChannel.CaChannelException:
|
||||
if retry <= 0:
|
||||
raise
|
||||
else:
|
||||
checked = 1
|
||||
|
||||
entry = cadict[name]
|
||||
if (timeout != None): entry.channel.setTimeout(timeout)
|
||||
if req_type == None:
|
||||
req_type=entry.field_type
|
||||
# kludge for broken DBR_CHAR
|
||||
if req_type == ca.DBR_CHAR:
|
||||
req_type = ca.DBR_INT
|
||||
if req_count == None:
|
||||
req_count = entry.element_count
|
||||
req_count = max(0, min(req_count, entry.element_count))
|
||||
try:
|
||||
val = entry.channel.getw(req_type=req_type, count=req_count)
|
||||
except CaChannel.CaChannelException:
|
||||
status = sys.exc_info()[1]
|
||||
#print "getw threw an exception (%s)" % status
|
||||
if ((int(status) == ca.ECA_BADTYPE) or (int(status) == ca.ECA_DISCONN)):
|
||||
# Delete dictionary entry. This clears the CA connection.
|
||||
print("caget: Repairing CA connection to ", name)
|
||||
del cadict[name]
|
||||
retry += 1
|
||||
if retry <= 0:
|
||||
raise
|
||||
else:
|
||||
success = 1
|
||||
return val
|
||||
|
||||
def isNumber(s):
|
||||
try:
|
||||
n = int(s)
|
||||
except:
|
||||
return False
|
||||
return True
|
||||
|
||||
#######################################################################
|
||||
def same(value, readback, native_readback, field_type, read_check_tolerance):
|
||||
"""For internal use by ca_util"""
|
||||
#print "ca_util.same(): field_type=%s" % field_type
|
||||
#print "ca_util.same(): value='%s'; readback='%s', native_readback='%s'" % (str(value), str(readback), str(native_readback))
|
||||
#print "ca_util.same(): type(value)=%s; type(readback)=%s, type(native_readback)=%s" % (type(value),
|
||||
# type(readback), type(native_readback))
|
||||
|
||||
if field_type in [ca.DBR_FLOAT, ca.DBR_DOUBLE]:
|
||||
return (abs(float(readback)-float(value)) < read_check_tolerance)
|
||||
elif field_type in [ca.DBR_INT, ca.DBR_SHORT, ca.DBR_LONG]:
|
||||
return (abs(int(readback)-int(value)) == 0)
|
||||
elif field_type == ca.DBR_ENUM:
|
||||
if str(value) == str(readback):
|
||||
return True
|
||||
if str(value) == str(native_readback):
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
return (str(value) == str(readback))
|
||||
|
||||
#######################################################################
|
||||
def caput(name, value, timeout=None, req_type=None, retries=None, read_check_tolerance=None):
|
||||
"""
|
||||
usage: caput("xxx:m1.VAL", new_value, timeout=None, req_type=None,
|
||||
retries=None, read_check_tolerance=None)
|
||||
Put a value, and optionally check that the value arrived safely.
|
||||
If read_check_tolerance == None (or is not supplied) then the default
|
||||
read-check tolerance is used. If read_check_tolerance == "NONE", then no
|
||||
read check is done.
|
||||
If read_check_tolerance != "NONE", then floating point numbers must be
|
||||
closer than the tolerance, and other types must agree exactly.
|
||||
Note that defaults for timeout, retries, and read_check_tolerance can be
|
||||
set for all ca_util functions, using the command set_ca_util_defaults().
|
||||
"""
|
||||
|
||||
_caput("caput", name, value, 0, timeout, req_type, retries, read_check_tolerance)
|
||||
|
||||
|
||||
#######################################################################
|
||||
def __ca_util_waitCB(epics_args, user_args):
|
||||
"""Function for internal use by caputw()."""
|
||||
#print "__ca_util_waitCB: %s done\n" % user_args[0]
|
||||
cadict[user_args[0]].callbackReceived = 1
|
||||
|
||||
#######################################################################
|
||||
def caputw(name, value, wait_timeout=None, timeout=None, req_type=None, retries=None,
|
||||
read_check_tolerance=None):
|
||||
"""
|
||||
usage: caputw("xxx:m1.VAL", new_value, wait_timeout=None, timeout=None,
|
||||
req_type=None, retries=None, read_check_tolerance=None)
|
||||
Put a value, optionally check that the value arrived safely, and wait (no
|
||||
longer than wait_timeout) for processing to complete. If
|
||||
read_check_tolerance == None (or is not supplied) then the default
|
||||
read-check tolerance is used. If read_check_tolerance == "NONE", then no
|
||||
read check is done. If read_check_tolerance != "NONE", then floating point
|
||||
numbers must be closer than the tolerance, and other types must agree
|
||||
exactly. Note that defaults for timeout, retries, and read_check_tolerance
|
||||
can be set for all ca_util functions, using the command
|
||||
set_ca_util_defaults().
|
||||
"""
|
||||
|
||||
_caput("caputw", name, value, wait_timeout, timeout, req_type, retries, read_check_tolerance)
|
||||
|
||||
|
||||
#######################################################################
|
||||
def _caput(function, name, value, wait_timeout=None, timeout=None, req_type=None, retries=None, read_check_tolerance=None):
|
||||
|
||||
global cadict, defaultTimeout, defaultRetries, readCheckTolerance
|
||||
|
||||
#print function
|
||||
if not name:
|
||||
print("%s: no PV name supplied" % function)
|
||||
raise ca_utilException(EXCEPTION_NULL_NAME)
|
||||
if ((timeout == None) and (defaultTimeout != None)): timeout = defaultTimeout
|
||||
if ((retries == None) and (defaultRetries != None)): retries = defaultRetries
|
||||
if ((retries == None) or (retries == "NONE")): retries = 0
|
||||
if ((read_check_tolerance == None) and (readCheckTolerance != None)):
|
||||
read_check_tolerance = readCheckTolerance
|
||||
|
||||
retries = max(retries,0)
|
||||
retry = retries + 1
|
||||
success = 0
|
||||
|
||||
checkName(name, timeout=timeout, retries=retries)
|
||||
|
||||
while ((not success) and (retry > 0)):
|
||||
|
||||
retry -= 1
|
||||
entry = cadict[name]
|
||||
|
||||
state = castate(name, timeout)
|
||||
#print "%s: state='%s'" % (function, state)
|
||||
if (state != 'connected'):
|
||||
print("%s: Repairing CA connection to '%s'" % (function, name))
|
||||
del cadict[name]
|
||||
retry += 1
|
||||
else:
|
||||
if req_type == None:
|
||||
req_type=entry.field_type
|
||||
if ((timeout != None) and (timeout != "NONE")): entry.channel.setTimeout(timeout)
|
||||
entry.callbackReceived = 0 # in case we're doing caputw()
|
||||
#value = convertToType(value, req_type)
|
||||
try:
|
||||
if function == "caput":
|
||||
entry.channel.putw(value, req_type=req_type)
|
||||
else: #caputw
|
||||
retval = entry.channel.array_put_callback(value,req_type,entry.element_count,__ca_util_waitCB,name)
|
||||
except CaChannel.CaChannelException:
|
||||
status = sys.exc_info()[1]
|
||||
print("put() threw an exception (%s)" % status)
|
||||
if ((int(status) == ca.ECA_BADTYPE) or (int(status) == ca.ECA_DISCONN)):
|
||||
# Delete dictionary entry. This clears the CA connection.
|
||||
print("%s: Repairing CA connection to '%s'" % (function, name))
|
||||
del cadict[name]
|
||||
retry += 1
|
||||
if retry <= 0:
|
||||
raise
|
||||
entry.callbackReceived = 1
|
||||
return
|
||||
else:
|
||||
if ((read_check_tolerance == None) or (read_check_tolerance == "NONE")):
|
||||
success = True
|
||||
else:
|
||||
if timeout:
|
||||
ca.pend_io(timeout)
|
||||
else:
|
||||
ca.pend_io(1.0)
|
||||
readback_success = False
|
||||
count = 0
|
||||
while ((not readback_success) and (count < retries+1)):
|
||||
try:
|
||||
readback = caget(name, req_type=req_type)
|
||||
native_readback = caget(name)
|
||||
readback_success = True
|
||||
if same(value, readback, native_readback, entry.field_type, read_check_tolerance):
|
||||
success = True
|
||||
#print "%s: Success\n" % (function)
|
||||
else:
|
||||
print("%s: readback '%s' disagrees with the value '%s' we wrote." % (function, readback, value))
|
||||
raise ca_utilException(EXCEPTION_READBACK_DISAGREES)
|
||||
entry.callbackReceived = 1
|
||||
except CaChannel.CaChannelException:
|
||||
print("%s: exception during readback." % (function))
|
||||
count += 1
|
||||
|
||||
if success and (function == "caputw"):
|
||||
start_time = time.time()
|
||||
timed_out = 0
|
||||
while (not entry.callbackReceived) and (not timed_out):
|
||||
#print "waiting for ", name
|
||||
time.sleep(0.1)
|
||||
#ca.pend_io(0.1)
|
||||
ca.poll()
|
||||
if (not wait_timeout):
|
||||
timed_out = 0
|
||||
else:
|
||||
timed_out = ((time.time()-start_time) > wait_timeout)
|
||||
|
||||
if not entry.callbackReceived:
|
||||
print("Execution not completed by wait_timeout (%d seconds)" % wait_timeout)
|
||||
|
||||
#######################################################################
|
||||
def camonitor(name, function, user_args=None, timeout=None, retries=None):
|
||||
"""
|
||||
usage: camonitor("xxx:m1.VAL", python_function, user_args, timeout=None,
|
||||
retries=None)
|
||||
Don't forget to call ca.pend_event(<pend_time_in_seconds>) periodically.
|
||||
"""
|
||||
|
||||
global defaultTimeout, defaultRetries
|
||||
|
||||
if not name:
|
||||
print("camonitor: no PV name supplied")
|
||||
raise ca_utilException(EXCEPTION_NULL_NAME)
|
||||
if not function:
|
||||
print("camonitor: no callback function supplied")
|
||||
raise ca_utilException(EXCEPTION_NULL_NAME)
|
||||
if not user_args: user_args = name
|
||||
if ((timeout==None) and (defaultTimeout != None)): timeout = defaultTimeout
|
||||
if ((retries==None) and (defaultRetries != None)): retries = defaultRetries
|
||||
if ((retries == None) or (retries == "NONE")): retries = 0
|
||||
|
||||
retries = max(retries,0)
|
||||
retry = retries + 1
|
||||
success = 0
|
||||
|
||||
while ((not success) and (retry > 0)):
|
||||
checked = 0
|
||||
while ((not checked) and (retry > 0)):
|
||||
retry -= 1
|
||||
try:
|
||||
checkName(name, timeout=timeout)
|
||||
except CaChannel.CaChannelException:
|
||||
if retry <= 0:
|
||||
raise
|
||||
else:
|
||||
checked = 1
|
||||
|
||||
entry = cadict[name]
|
||||
if ((timeout != None) and (timeout != "NONE")): entry.channel.setTimeout(timeout)
|
||||
try:
|
||||
entry.channel.add_masked_array_event(entry.field_type,entry.element_count,ca.DBE_VALUE, function, user_args)
|
||||
except CaChannel.CaChannelException:
|
||||
status = sys.exc_info()[1]
|
||||
#print "add_masked_array_event threw an exception (%s)" % status
|
||||
if ((int(status) == ca.ECA_BADTYPE) or (int(status) == ca.ECA_DISCONN)):
|
||||
# Delete dictionary entry. This clears the CA connection.
|
||||
print("camonitor: Repairing CA connection to %s" % name)
|
||||
del cadict[name]
|
||||
retry += 1
|
||||
if retry <= 0:
|
||||
raise
|
||||
else:
|
||||
success = 1
|
||||
|
||||
#######################################################################
|
||||
def caunmonitor(name, timeout=None):
|
||||
"""usage: caunmonitor("xxx:m1.VAL", timeout=None)"""
|
||||
|
||||
global defaultTimeout
|
||||
|
||||
if not name:
|
||||
print("caunmonitor: no PV name supplied")
|
||||
raise ca_utilException(EXCEPTION_NULL_NAME)
|
||||
if ((timeout==None) and (defaultTimeout != None)): timeout = defaultTimeout
|
||||
|
||||
if name not in cadict:
|
||||
print("ca_util has no connection to '%s'" % name)
|
||||
raise ca_utilException(EXCEPTION_NOT_CONNECTED)
|
||||
|
||||
channel = cadict[name].channel
|
||||
if ((timeout != None) and (timeout != "NONE")): channel.setTimeout(timeout)
|
||||
try:
|
||||
channel.clear_event()
|
||||
except CaChannel.CaChannelException:
|
||||
status = sys.exc_info()[1]
|
||||
print("caunmonitor: CaChannel exception, status=%d (%s)" % (status, ca.message(status)))
|
||||
return
|
||||
|
||||
#######################################################################
|
||||
def test_monitor_function(epics_args, user_args):
|
||||
"""Example callback routine for use with camonitor()."""
|
||||
print('test_monitor_function:')
|
||||
print("...epics_args: %s" % repr(epics_args))
|
||||
print("...user_args: %s" % repr(user_args))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------------------
|
||||
# miscellaneous functions that might be useful, but haven't been integrated into the package
|
||||
|
||||
#######################################################################
|
||||
def endianUs():
|
||||
"""
|
||||
usage: endianUs()
|
||||
Returns one of "Little Endian", "Big Endian", "Unknown Endian".
|
||||
"""
|
||||
|
||||
from struct import pack
|
||||
if pack('<h', 1) == pack('=h',1):
|
||||
return "Little Endian"
|
||||
elif pack('>h', 1) == pack('=h',1):
|
||||
return "Big Endian"
|
||||
else:
|
||||
return "Unknown Endian"
|
||||
|
||||
#######################################################################
|
||||
def printExceptionInfo(maxTBlevel=15):
|
||||
"""Intended for internal use by ca_util functions."""
|
||||
|
||||
import sys, traceback
|
||||
cla, exc, trbk = sys.exc_info()
|
||||
excName = cla.__name__
|
||||
try:
|
||||
excArgs = exc.__dict__["args"]
|
||||
except KeyError:
|
||||
excArgs = "<no args>"
|
||||
excTb = traceback.format_tb(trbk, maxTBlevel)
|
||||
print("Unanticipated exception: %s %s\n" % (excName, excArgs))
|
||||
if (len(excTb) > 0):
|
||||
print("Traceback:")
|
||||
for trace in excTb:
|
||||
print(trace)
|
||||
return
|
||||
@@ -0,0 +1,189 @@
|
||||
""" @package cadefs
|
||||
contstants and enumerated conststant
|
||||
|
||||
This defines constants and classes useful to inteprete code returned from CA library.
|
||||
"""
|
||||
#
|
||||
CA_OP_GET = 0
|
||||
CA_OP_PUT = 1
|
||||
CA_OP_CREATE_CHANNEL = 2
|
||||
CA_OP_ADD_EVENT = 3
|
||||
CA_OP_CLEAR_EVENT = 4
|
||||
CA_OP_OTHER = 5
|
||||
# used with connection callbacks
|
||||
CA_OP_CONN_UP = 6
|
||||
CA_OP_CONN_DOWN = 7
|
||||
# imported from caeventmask.h
|
||||
DBE_VALUE =(1<<0)
|
||||
DBE_LOG =(1<<1)
|
||||
DBE_ALARM =(1<<2)
|
||||
DBE_PROPERTY=(1<<3)
|
||||
# also chekc ECA_IODONE/ECA_IOINPROGRESS in caError.py
|
||||
IODONE = 42
|
||||
IOINPROGRESS = 43
|
||||
#
|
||||
DBF_NATIVE=-1
|
||||
DBF_STRING=0
|
||||
DBF_INT = 1
|
||||
DBF_SHORT =1
|
||||
DBF_FLOAT =2
|
||||
DBF_ENUM =3
|
||||
DBF_CHAR =4
|
||||
DBF_LONG = 5
|
||||
DBF_DOUBLE = 6
|
||||
DBF_NO_ACCES = 7
|
||||
LAST_TYPE = DBF_DOUBLE
|
||||
|
||||
def VALID_DB_FIELD(x):
|
||||
return ((x >= 0) and (x <= LAST_TYPE))
|
||||
def INVALID_DB_FIELD(x):
|
||||
return ((x < 0) or (x > LAST_TYPE))
|
||||
|
||||
#/* data request buffer types */
|
||||
DBR_NATIVE= DBF_NATIVE
|
||||
DBR_STRING = DBF_STRING
|
||||
DBR_INT = DBF_INT
|
||||
DBR_SHORT = DBF_INT
|
||||
DBR_FLOAT = DBF_FLOAT
|
||||
DBR_ENUM = DBF_ENUM
|
||||
DBR_CHAR = DBF_CHAR
|
||||
DBR_LONG = DBF_LONG
|
||||
DBR_DOUBLE = DBF_DOUBLE
|
||||
DBR_STS_STRING = 7
|
||||
DBR_STS_SHORT = 8
|
||||
DBR_STS_INT = DBR_STS_SHORT
|
||||
DBR_STS_FLOAT = 9
|
||||
DBR_STS_ENUM = 10
|
||||
DBR_STS_CHAR = 11
|
||||
DBR_STS_LONG = 12
|
||||
DBR_STS_DOUBLE = 13
|
||||
DBR_TIME_STRING = 14
|
||||
DBR_TIME_INT = 15
|
||||
DBR_TIME_SHORT = 15
|
||||
DBR_TIME_FLOAT = 16
|
||||
DBR_TIME_ENUM = 17
|
||||
DBR_TIME_CHAR = 18
|
||||
DBR_TIME_LONG = 19
|
||||
DBR_TIME_DOUBLE = 20
|
||||
DBR_GR_STRING = 21
|
||||
DBR_GR_SHORT = 22
|
||||
DBR_GR_INT = DBR_GR_SHORT
|
||||
DBR_GR_FLOAT = 23
|
||||
DBR_GR_ENUM = 24
|
||||
DBR_GR_CHAR = 25
|
||||
DBR_GR_LONG = 26
|
||||
DBR_GR_DOUBLE = 27
|
||||
DBR_CTRL_STRING = 28
|
||||
DBR_CTRL_SHORT = 29
|
||||
DBR_CTRL_INT = DBR_CTRL_SHORT
|
||||
DBR_CTRL_FLOAT = 30
|
||||
DBR_CTRL_ENUM = 31
|
||||
DBR_CTRL_CHAR = 32
|
||||
DBR_CTRL_LONG = 33
|
||||
DBR_CTRL_DOUBLE = 34
|
||||
DBR_PUT_ACKT = DBR_CTRL_DOUBLE + 1
|
||||
DBR_PUT_ACKS = DBR_PUT_ACKT + 1
|
||||
DBR_STSACK_STRING = DBR_PUT_ACKS + 1
|
||||
LAST_BUFFER_TYPE = DBR_STSACK_STRING
|
||||
|
||||
def VALID_DB_REQ(x):
|
||||
return ((x >= 0) and (x <= LAST_BUFFER_TYPE))
|
||||
def INVALID_DB_REQ(x):
|
||||
return ((x < 0) or (x > LAST_BUFFER_TYPE))
|
||||
|
||||
class AlarmSeverity:
|
||||
"""Alarm Severity class
|
||||
|
||||
AlarmSeverity class is provided to keep constants representing EPICS channel severity status.
|
||||
It also keeps strings and colors for each severity states.
|
||||
"""
|
||||
NO_ALARM =0x0
|
||||
MINOR_ALARM=0x1
|
||||
MAJOR_ALARM=0x2
|
||||
INVALID_ALARM=0x3
|
||||
ALARM_NSEV=INVALID_ALARM+1
|
||||
Strings=(
|
||||
"NO_ALARM",
|
||||
"MINOR",
|
||||
"MAJOR",
|
||||
"INVALID",
|
||||
)
|
||||
Colors=("green","yellow","red","grey")
|
||||
|
||||
class AlarmStatus:
|
||||
"""!
|
||||
AlarmStatus class provides constants returned by EPICS Channe Access library as channel status code.
|
||||
It also gives you strings for corresponding channel status.
|
||||
"""
|
||||
NO_ALARM = 0
|
||||
READ_ALARM = 1
|
||||
WRITE_ALARM = 2
|
||||
#/* ANALOG ALARMS */
|
||||
HIHI_ALARM = 3
|
||||
HIGH_ALARM = 4
|
||||
LOLO_ALARM = 5
|
||||
LOW_ALARM = 6
|
||||
#/* BINARY ALARMS */
|
||||
STATE_ALARM = 7
|
||||
COS_ALARM = 8
|
||||
#/* other alarms */
|
||||
COMM_ALARM = 9
|
||||
TIMEOUT_ALARM = 10
|
||||
HW_LIMIT_ALARM = 11
|
||||
CALC_ALARM = 12
|
||||
SCAN_ALARM = 13
|
||||
LINK_ALARM = 14
|
||||
SOFT_ALARM = 15
|
||||
BAD_SUB_ALARM = 16
|
||||
UDF_ALARM = 17
|
||||
DISABLE_ALARM = 18
|
||||
SIMM_ALARM = 19
|
||||
READ_ACCESS_ALARM = 20
|
||||
WRITE_ACCESS_ALARM = 21
|
||||
Strings=(
|
||||
"NO_ALARM",
|
||||
"READ",
|
||||
"WRITE",
|
||||
"HIHI",
|
||||
"HIGH",
|
||||
"LOLO",
|
||||
"LOW",
|
||||
"STATE",
|
||||
"COS",
|
||||
"COMM",
|
||||
"TIMEOUT",
|
||||
"HWLIMIT",
|
||||
"CALC",
|
||||
"SCAN",
|
||||
"LINK",
|
||||
"SOFT",
|
||||
"BAD_SUB",
|
||||
"UDF",
|
||||
"DISABLE",
|
||||
"SIMM",
|
||||
"READ_ACCESS",
|
||||
"WRITE_ACCESS",
|
||||
)
|
||||
|
||||
|
||||
# ch_state={cs_never_conn=0, cs_prev_conn, cs_conn, cs_closed}
|
||||
|
||||
cs_never_conn= 0
|
||||
cs_prev_conn = 1
|
||||
cs_conn = 2
|
||||
cs_closed = 3
|
||||
|
||||
class ch_state:
|
||||
"""
|
||||
ch_state class provides constants representing channel connection status.
|
||||
"""
|
||||
cs_never_conn= 0
|
||||
cs_prev_conn = 1
|
||||
cs_conn = 2
|
||||
cs_closed = 3
|
||||
Strings=(
|
||||
"channel never connected",
|
||||
"channel previously connected",
|
||||
"channel connected",
|
||||
"channel already closed",
|
||||
)
|
||||
@@ -0,0 +1,357 @@
|
||||
"""
|
||||
This module provides support for the EPICS motor record.
|
||||
|
||||
Author: Mark Rivers
|
||||
Created: Sept. 16, 2002
|
||||
Modifications:
|
||||
"""
|
||||
import time
|
||||
|
||||
import epicsPV
|
||||
|
||||
class epicsMotor:
|
||||
"""
|
||||
This module provides a class library for the EPICS motor record.
|
||||
It uses the epicsPV class, which is in turn a subclass of CaChannel.
|
||||
|
||||
Virtual attributes:
|
||||
These attributes do not appear in the dictionary for this class, but
|
||||
are implemented with the __getattr__ and __setattr__ methods. They
|
||||
simply do getw() or putw(value) to the appropriate motor record fields.
|
||||
All attributes can be both read and written unless otherwise noted.
|
||||
|
||||
Attribute Description Field
|
||||
--------- ----------------------- -----
|
||||
slew_speed Slew speed or velocity .VELO
|
||||
base_speed Base or starting speed .VBAS
|
||||
acceleration Acceleration time (sec) .ACCL
|
||||
description Description of motor .DESC
|
||||
resolution Resolution (units/step) .MRES
|
||||
high_limit High soft limit (user) .HLM
|
||||
low_limit Low soft limit (user) .LLM
|
||||
dial_high_limit High soft limit (dial) .DHLM
|
||||
dial_low_limit Low soft limit (dial) .DLLM
|
||||
backlash Backlash distance .BDST
|
||||
offset Offset from dial to user .OFF
|
||||
done_moving 1=Done, 0=Moving, read-only .DMOV
|
||||
|
||||
Exceptions:
|
||||
The check_limits() method raises an "epicsMotorException" if a soft limit
|
||||
or hard limit is detected. The move() and wait() methods call
|
||||
check_limits() before they return, unless they are called with the
|
||||
ignore_limits=1 keyword set.
|
||||
|
||||
Example use:
|
||||
from epicsMotor import *
|
||||
m=epicsMotor('13BMD:m38')
|
||||
m.move(10) # Move to position 10 in user coordinates
|
||||
m.move(100, dial=1) # Move to position 100 in dial coordinates
|
||||
m.move(1, step=1, relative=1) # Move 1 step relative to current position
|
||||
m.wait() # Wait for motor to stop moving
|
||||
m.wait(start=1) # Wait for motor to start moving
|
||||
m.wait(start=1, stop=1) # Wait for motor to start, then to stop
|
||||
m.stop() # Stop moving immediately
|
||||
high = m.high_limit # Get the high soft limit in user coordinates
|
||||
m.dial_high_limit = 100 # Set the high limit to 100 in dial coodinates
|
||||
speed = m.slew_speed # Get the slew speed
|
||||
m.acceleration = 0.1 # Set the acceleration to 0.1 seconds
|
||||
p=m.get_position() # Get the desired motor position in user coordinates
|
||||
p=m.get_position(dial=1) # Get the desired motor position in dial coordinates
|
||||
p=m.get_position(readback=1) # Get the actual position in user coordinates
|
||||
p=m.get_position(readback=1, step=1) Get the actual motor position in steps
|
||||
p=m.set_position(100) # Set the current position to 100 in user coordinates
|
||||
# Puts motor in Set mode, writes value, puts back in Use mode.
|
||||
p=m.set_position(10000, step=1) # Set the current position to 10000 steps
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
"""
|
||||
Creates a new epicsMotor instance.
|
||||
|
||||
Inputs:
|
||||
name:
|
||||
The name of the EPICS motor record without any trailing period or field
|
||||
name.
|
||||
|
||||
Example:
|
||||
m=epicsMotor('13BMD:m38')
|
||||
"""
|
||||
self.pvs = {'val' : epicsPV.epicsPV(name+'.VAL', wait=0),
|
||||
'dval': epicsPV.epicsPV(name+'.DVAL', wait=0),
|
||||
'rval': epicsPV.epicsPV(name+'.RVAL', wait=0),
|
||||
'rlv' : epicsPV.epicsPV(name+'.RLV', wait=0),
|
||||
'rbv' : epicsPV.epicsPV(name+'.RBV', wait=0),
|
||||
'drbv': epicsPV.epicsPV(name+'.DRBV', wait=0),
|
||||
'rrbv': epicsPV.epicsPV(name+'.RRBV', wait=0),
|
||||
'dmov': epicsPV.epicsPV(name+'.DMOV', wait=0),
|
||||
'stop': epicsPV.epicsPV(name+'.STOP', wait=0),
|
||||
'velo': epicsPV.epicsPV(name+'.VELO', wait=0),
|
||||
'vbas': epicsPV.epicsPV(name+'.VBAS', wait=0),
|
||||
'accl': epicsPV.epicsPV(name+'.ACCL', wait=0),
|
||||
'desc': epicsPV.epicsPV(name+'.DESC', wait=0),
|
||||
'mres': epicsPV.epicsPV(name+'.MRES', wait=0),
|
||||
'hlm': epicsPV.epicsPV(name+'.HLM', wait=0),
|
||||
'llm': epicsPV.epicsPV(name+'.LLM', wait=0),
|
||||
'dhlm': epicsPV.epicsPV(name+'.DHLM', wait=0),
|
||||
'dllm': epicsPV.epicsPV(name+'.DLLM', wait=0),
|
||||
'bdst': epicsPV.epicsPV(name+'.BDST', wait=0),
|
||||
'set': epicsPV.epicsPV(name+'.SET', wait=0),
|
||||
'lvio': epicsPV.epicsPV(name+'.LVIO', wait=0),
|
||||
'lls': epicsPV.epicsPV(name+'.LLS', wait=0),
|
||||
'hls': epicsPV.epicsPV(name+'.HLS', wait=0),
|
||||
'off': epicsPV.epicsPV(name+'.OFF', wait=0)
|
||||
}
|
||||
# Wait for all PVs to connect
|
||||
self.pvs['val'].pend_io()
|
||||
|
||||
def move(self, value, relative=0, dial=0, step=0, ignore_limits=0):
|
||||
"""
|
||||
Moves a motor to an absolute position or relative to the current position
|
||||
in user, dial or step coordinates.
|
||||
|
||||
Inputs:
|
||||
value:
|
||||
The absolute position or relative amount of the move
|
||||
|
||||
Keywords:
|
||||
relative:
|
||||
Set relative=1 to move relative to current position.
|
||||
The default is an absolute move.
|
||||
|
||||
dial:
|
||||
Set dial=1 if "value" is in dial coordinates.
|
||||
The default is user coordinates.
|
||||
|
||||
step:
|
||||
Set step=1 if "value" is in steps.
|
||||
The default is user coordinates.
|
||||
|
||||
ignore_limits:
|
||||
Set ignore_limits=1 to suppress raising exceptions
|
||||
if the move results in a soft or hard limit violation.
|
||||
|
||||
Notes:
|
||||
The "step" and "dial" keywords are mutually exclusive.
|
||||
The "relative" keyword can be used in user, dial or step
|
||||
coordinates.
|
||||
|
||||
Examples:
|
||||
m=epicsMotor('13BMD:m38')
|
||||
m.move(10) # Move to position 10 in user coordinates
|
||||
m.move(100, dial=1) # Move to position 100 in dial coordinates
|
||||
m.move(2, step=1, relative=1) # Move 2 steps
|
||||
"""
|
||||
if (dial != 0):
|
||||
# Position in dial coordinates
|
||||
if (relative != 0):
|
||||
current = self.get_position(dial=1)
|
||||
self.pvs['dval'].putw(current+value)
|
||||
else:
|
||||
self.pvs['dval'].putw(value)
|
||||
|
||||
elif (step != 0):
|
||||
# Position in steps
|
||||
if (relative != 0):
|
||||
current = self.get_position(step=1)
|
||||
self.pvs['rval'].putw(current + value)
|
||||
else:
|
||||
self.pvs['rval'].putw(value)
|
||||
else:
|
||||
# Position in user coordinates
|
||||
if (relative != 0):
|
||||
self.pvs['rlv'].putw(value)
|
||||
else:
|
||||
self.pvs['val'].putw(value)
|
||||
|
||||
# Check for limit violations
|
||||
if (ignore_limits == 0): self.check_limits()
|
||||
|
||||
def check_limits(self):
|
||||
limit = self.pvs['lvio'].getw()
|
||||
if (limit!=0):
|
||||
raise epicsMotorException('Soft limit violation')
|
||||
limit = self.pvs['lls'].getw()
|
||||
if (limit!=0):
|
||||
raise epicsMotorException('Low hard limit violation')
|
||||
limit = self.pvs['hls'].getw()
|
||||
if (limit!=0):
|
||||
raise epicsMotorException('High hard limit violation')
|
||||
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Immediately stops a motor from moving by writing 1 to the .STOP field.
|
||||
|
||||
Examples:
|
||||
m=epicsMotor('13BMD:m38')
|
||||
m.move(10) # Move to position 10 in user coordinates
|
||||
m.stop() # Stop motor
|
||||
"""
|
||||
self.pvs['stop'].putw(1)
|
||||
|
||||
def get_position(self, dial=0, readback=0, step=0):
|
||||
"""
|
||||
Returns the target or readback motor position in user, dial or step
|
||||
coordinates.
|
||||
|
||||
Keywords:
|
||||
readback:
|
||||
Set readback=1 to return the readback position in the
|
||||
desired coordinate system. The default is to return the
|
||||
target position of the motor.
|
||||
|
||||
dial:
|
||||
Set dial=1 to return the position in dial coordinates.
|
||||
The default is user coordinates.
|
||||
|
||||
step:
|
||||
Set step=1 to return the position in steps.
|
||||
The default is user coordinates.
|
||||
|
||||
Notes:
|
||||
The "step" and "dial" keywords are mutually exclusive.
|
||||
The "readback" keyword can be used in user, dial or step
|
||||
coordinates.
|
||||
|
||||
Examples:
|
||||
m=epicsMotor('13BMD:m38')
|
||||
m.move(10) # Move to position 10 in user coordinates
|
||||
p=m.get_position(dial=1) # Read the target position in
|
||||
# dial coordinates
|
||||
p=m.get_position(readback=1, step=1) # Read the actual position in
|
||||
# steps
|
||||
"""
|
||||
if (dial != 0):
|
||||
if (readback != 0):
|
||||
return self.pvs['drbv'].getw()
|
||||
else:
|
||||
return self.pvs['dval'].getw()
|
||||
elif (step != 0):
|
||||
if (readback != 0):
|
||||
return self.pvs['rrbv'].getw()
|
||||
else:
|
||||
return self.pvs['rval'].getw()
|
||||
else:
|
||||
if (readback != 0):
|
||||
return self.pvs['rbv'].getw()
|
||||
else:
|
||||
return self.pvs['val'].getw()
|
||||
|
||||
def set_position(self, position, dial=0, step=0):
|
||||
"""
|
||||
Sets the motor position in user, dial or step coordinates.
|
||||
|
||||
Inputs:
|
||||
position:
|
||||
The new motor position
|
||||
|
||||
Keywords:
|
||||
dial:
|
||||
Set dial=1 to set the position in dial coordinates.
|
||||
The default is user coordinates.
|
||||
|
||||
step:
|
||||
Set step=1 to set the position in steps.
|
||||
The default is user coordinates.
|
||||
|
||||
Notes:
|
||||
The "step" and "dial" keywords are mutually exclusive.
|
||||
|
||||
Examples:
|
||||
m=epicsMotor('13BMD:m38')
|
||||
m.set_position(10, dial=1) # Set the motor position to 10 in
|
||||
# dial coordinates
|
||||
m.set_position(1000, step=1) # Set the motor position to 1000 steps
|
||||
"""
|
||||
# Put the motor in "SET" mode
|
||||
self.pvs['set'].putw(1)
|
||||
if (dial != 0):
|
||||
self.pvs['dval'].putw(position)
|
||||
elif (step != 0):
|
||||
self.pvs['rval'].putw(position)
|
||||
else:
|
||||
self.pvs['val'].putw(position)
|
||||
# Put the motor back in "Use" mode
|
||||
self.pvs['set'].putw(0)
|
||||
|
||||
|
||||
def wait(self, start=0, stop=0, poll=0.01, ignore_limits=0):
|
||||
"""
|
||||
Waits for the motor to start moving and/or stop moving.
|
||||
|
||||
Keywords:
|
||||
start:
|
||||
Set start=1 to wait for the motor to start moving.
|
||||
|
||||
stop:
|
||||
Set stop=1 to wait for the motor to stop moving.
|
||||
|
||||
poll:
|
||||
Set this keyword to the time to wait between reading the
|
||||
.DMOV field of the record to see if the motor is moving.
|
||||
The default is 0.01 seconds.
|
||||
|
||||
ignore_limits:
|
||||
Set ignore_limits=1 to suppress raising an exception if a soft or
|
||||
hard limit is detected
|
||||
|
||||
Notes:
|
||||
If neither the "start" nor "stop" keywords are set then "stop"
|
||||
is set to 1, so the routine waits for the motor to stop moving.
|
||||
If only "start" is set to 1 then the routine only waits for the
|
||||
motor to start moving.
|
||||
If both "start" and "stop" are set to 1 then the routine first
|
||||
waits for the motor to start moving, and then to stop moving.
|
||||
|
||||
Examples:
|
||||
m=epicsMotor('13BMD:m38')
|
||||
m.move(100) # Move to position 100
|
||||
m.wait(start=1, stop=1) # Wait for the motor to start moving
|
||||
# and then to stop moving
|
||||
"""
|
||||
if (start == 0) and (stop == 0): stop=1
|
||||
if (start != 0):
|
||||
while(1):
|
||||
done = self.pvs['dmov'].getw()
|
||||
if (done != 1): break
|
||||
time.sleep(poll)
|
||||
if (stop != 0):
|
||||
while(1):
|
||||
done = self.pvs['dmov'].getw()
|
||||
if (done != 0): break
|
||||
time.sleep(poll)
|
||||
if (ignore_limits == 0): self.check_limits()
|
||||
|
||||
def __getattr__(self, attrname):
|
||||
if (attrname == 'slew_speed'): return self.pvs['velo'].getw()
|
||||
elif (attrname == 'base_speed'): return self.pvs['vbas'].getw()
|
||||
elif (attrname == 'acceleration'): return self.pvs['accl'].getw()
|
||||
elif (attrname == 'description'): return self.pvs['desc'].getw()
|
||||
elif (attrname == 'resolution'): return self.pvs['mres'].getw()
|
||||
elif (attrname == 'high_limit'): return self.pvs['hlm'].getw()
|
||||
elif (attrname == 'low_limit'): return self.pvs['llm'].getw()
|
||||
elif (attrname == 'dial_high_limit'): return self.pvs['dhlm'].getw()
|
||||
elif (attrname == 'dial_low_limit'): return self.pvs['dllm'].getw()
|
||||
elif (attrname == 'backlash'): return self.pvs['bdst'].getw()
|
||||
elif (attrname == 'offset'): return self.pvs['off'].getw()
|
||||
elif (attrname == 'done_moving'): return self.pvs['dmov'].getw()
|
||||
else: raise AttributeError(attrname)
|
||||
|
||||
def __setattr__(self, attrname, value):
|
||||
if (attrname == 'pvs'): self.__dict__[attrname]=value
|
||||
elif (attrname == 'slew_speed'): self.pvs['velo'].putw(value)
|
||||
elif (attrname == 'base_speed'): self.pvs['vbas'].putw(value)
|
||||
elif (attrname == 'acceleration'): self.pvs['accl'].putw(value)
|
||||
elif (attrname == 'description'): self.pvs['desc'].putw(value)
|
||||
elif (attrname == 'resolution'): self.pvs['mres'].putw(value)
|
||||
elif (attrname == 'high_limit'): self.pvs['hlm'].putw(value)
|
||||
elif (attrname == 'low_limit'): self.pvs['llm'].putw(value)
|
||||
elif (attrname == 'dial_high_limit'): self.pvs['dhlm'].putw(value)
|
||||
elif (attrname == 'dial_low_limit'): self.pvs['dllm'].putw(value)
|
||||
elif (attrname == 'backlash'): self.pvs['bdst'].putw(value)
|
||||
elif (attrname == 'offset'): self.pvs['off'].putw(value)
|
||||
else: raise AttributeError(attrname)
|
||||
|
||||
class epicsMotorException(Exception):
|
||||
def __init__(self, args=None):
|
||||
self.args=args
|
||||
@@ -0,0 +1,273 @@
|
||||
"""
|
||||
This module defines the epicsPV class, which adds additional features to
|
||||
Geoff Savage's CaChannel class.
|
||||
|
||||
Author: Mark Rivers
|
||||
Created: Sept. 16, 2002.
|
||||
Modifications:
|
||||
"""
|
||||
import CaChannel
|
||||
|
||||
class epicsPV(CaChannel.CaChannel):
|
||||
"""
|
||||
This class subclasses Geoff Savage's CaChannel class to add the following
|
||||
features:
|
||||
|
||||
- If a PV name is given then the class constructor will do a searchw()
|
||||
by default.
|
||||
|
||||
- setMonitor() sets a generic callback routine for value change events.
|
||||
Subsequent getw(), getValue() or array_get() calls will return the
|
||||
value from the most recent callback, and hence do not result in any
|
||||
network activity or latency. This can greatly improve performance.
|
||||
|
||||
- checkMonitor() returns a flag to indicate if a callback has occured
|
||||
since the last call to checkMonitor(), getw(), getValue() or
|
||||
array_get(). It can be used to increase efficiency in polling
|
||||
applications.
|
||||
|
||||
- getControl() reads the "control" and other information from an
|
||||
EPICS PV without having to use callbacks.
|
||||
In addition to the PV value, this will return the graphic, control and
|
||||
alarm limits, etc.
|
||||
|
||||
- putWait() calls array_put_callback() and waits for the callback to
|
||||
occur before it returns. This allows programs to use array_put_callback()
|
||||
synchronously and without user-written callbacks.
|
||||
|
||||
Created: Mark Rivers, Sept. 16, 2002.
|
||||
Modifications:
|
||||
"""
|
||||
|
||||
def __init__(self, pvName=None, wait=1):
|
||||
"""
|
||||
Keywords:
|
||||
pvName:
|
||||
An optional name of an EPICS Process Variable.
|
||||
|
||||
wait:
|
||||
If wait==1 and pvName != None then this constructor will do a
|
||||
CaChannel.searchw() on the PV. If wait==0 and pvName != None then
|
||||
this constructor will do a CaChannel.search() on the PV, and the user
|
||||
must subsequently do a pend_io() on this or another epicsPV or CaChannel
|
||||
object.
|
||||
|
||||
Procedure:
|
||||
Invokes CaChannel.__init__() and then searchw() or search() as explained
|
||||
above
|
||||
"""
|
||||
# Invoke the base class initialization
|
||||
self.callBack = callBack()
|
||||
CaChannel.CaChannel.__init__(self)
|
||||
if (pvName != None):
|
||||
if (wait):
|
||||
self.searchw(pvName)
|
||||
else:
|
||||
self.search(pvName)
|
||||
|
||||
def setMonitor(self):
|
||||
"""
|
||||
Sets a generic callback routine for value change events.
|
||||
Subsequent getw(), getValue() or array_get() calls will return the
|
||||
value from the most recent callback, do not result in any network
|
||||
latency. This can greatly improve efficiency.
|
||||
"""
|
||||
self.add_masked_array_event(None, None, CaChannel.ca.DBE_VALUE,
|
||||
getCallback, self.callBack)
|
||||
self.callBack.monitorState = 1
|
||||
|
||||
def clearMonitor(self):
|
||||
"""
|
||||
Cancels the effect of a previous call to setMonitor().
|
||||
Calls CaChannel.clear_event().
|
||||
Subsequent getw(), getValue() or array_get() calls will no longer
|
||||
return the value from the most recent callback, but will actually result
|
||||
in channel access calls.
|
||||
"""
|
||||
self.clear_event()
|
||||
self.callBack.monitorState = 0
|
||||
|
||||
def checkMonitor(self):
|
||||
"""
|
||||
Returns 1 to indicate if a value callback has occured
|
||||
since the last call to checkMonitor(), getw(), getValue() or
|
||||
array_get(), indicating that a new value is available. Returns 0 if
|
||||
no such callback has occurred.
|
||||
It can be used to increase efficiency in polling applications.
|
||||
"""
|
||||
# This should be self.poll(), but that is generating errors
|
||||
self.pend_event(.0001)
|
||||
m = self.callBack.newMonitor
|
||||
self.callBack.newMonitor = 0
|
||||
return m
|
||||
|
||||
def getControl(self, req_type=None, count=None, wait=1, poll=.01):
|
||||
"""
|
||||
Provides a method to read the "control" and other information from an
|
||||
EPICS PV without having to use callbacks.
|
||||
It calls CaChannel.array_get_callback() with a database request type of
|
||||
CaChannel.ca.dbf_type_to_DBR_CTRL(req_type).
|
||||
In addition to the PV value, this will return the graphic, control and
|
||||
alarm limits, etc.
|
||||
|
||||
Example:
|
||||
>>> pv = epicsPV('13IDC:m1')
|
||||
>>> pv.getControl()
|
||||
>>> for field in dir(pv.callBack):
|
||||
>>> print field, ':', getattr(pv.callBack, field)
|
||||
chid : _bfffec34_chid_p
|
||||
count : 1
|
||||
monitorState : 0
|
||||
newMonitor : 1
|
||||
putComplete : 0
|
||||
pv_loalarmlim : 0.0
|
||||
pv_loctrllim : -22.0
|
||||
pv_lodislim : -22.0
|
||||
pv_lowarnlim : 0.0
|
||||
pv_precision : 4
|
||||
pv_riscpad0 : 256
|
||||
pv_severity : 0
|
||||
pv_status : 0
|
||||
pv_units : mm
|
||||
pv_upalarmlim : 0.0
|
||||
pv_upctrllim : 28.0
|
||||
pv_updislim : 28.0
|
||||
pv_upwarnlim : 0.0
|
||||
pv_value : -15.0
|
||||
status : 1
|
||||
type : 34
|
||||
|
||||
Note the fields such as pv_plocrtllim, the lower control limit, and
|
||||
pv_precision, the display precision.
|
||||
|
||||
Keywords:
|
||||
wait:
|
||||
If this keyword is 1 (the default) then this routine waits for
|
||||
the callback before returning. If this keyword is 0 then it is
|
||||
the user's responsibility to wait or check for the callback
|
||||
by calling checkMonitor().
|
||||
|
||||
poll:
|
||||
The timeout for pend_event() calls, waiting for the callback
|
||||
to occur. Shorter times reduce the latency at the price of CPU
|
||||
cycles.
|
||||
"""
|
||||
if (req_type == None): req_type = self.field_type()
|
||||
if (wait != 0): self.callBack.newMonitor = 0
|
||||
self.array_get_callback(CaChannel.ca.dbf_type_to_DBR_CTRL(req_type),
|
||||
count, getCallback, self.callBack)
|
||||
if (wait != 0):
|
||||
while(self.callBack.newMonitor == 0):
|
||||
self.pend_event(poll)
|
||||
|
||||
def array_get(self, req_type=None, count=None):
|
||||
"""
|
||||
If setMonitor() has not been called then this function simply calls
|
||||
CaChannel.array_get(). If setMonitor has been called then it calls
|
||||
CaChannel.pend_event() with a very short timeout, and then returns the
|
||||
PV value from the last callback.
|
||||
"""
|
||||
if (self.callBack.monitorState != 0):
|
||||
# This should be self.poll(), but that is generating errors
|
||||
self.pend_event(.0001)
|
||||
if (self.callBack.monitorState == 2):
|
||||
self.callBack.newMonitor = 0
|
||||
return self.callBack.pv_value
|
||||
else:
|
||||
return CaChannel.CaChannel.array_get(self, req_type, count)
|
||||
|
||||
def getw(self, req_type=None, count=None):
|
||||
"""
|
||||
If setMonitor() has not been called then this function simply calls
|
||||
CaChannel.getw(). If setMonitor has been called then it calls
|
||||
CaChannel.pend_event() with a very short timeout, and then returns the
|
||||
PV value from the last callback.
|
||||
"""
|
||||
if (self.callBack.monitorState != 0):
|
||||
# This should be self.poll(), but that is generating errors
|
||||
self.pend_event(.0001)
|
||||
if (self.callBack.monitorState == 2):
|
||||
self.callBack.newMonitor = 0
|
||||
if (count == None):
|
||||
return self.callBack.pv_value
|
||||
else:
|
||||
return self.callBack.pv_value[0:count]
|
||||
else:
|
||||
return CaChannel.CaChannel.getw(self, req_type, count)
|
||||
|
||||
def getValue(self):
|
||||
"""
|
||||
If setMonitor() has not been called then this function simply calls
|
||||
CaChannel.getValue(). If setMonitor has been called then it calls
|
||||
CaChannel.pend_event() with a very short timeout, and then returns the
|
||||
PV value from the last callback.
|
||||
"""
|
||||
if (self.callBack.monitorState != 0):
|
||||
# This should be self.poll(), but that is generating errors
|
||||
self.pend_event(.0001)
|
||||
if (self.callBack.monitorState == 2):
|
||||
self.callBack.newMonitor = 0
|
||||
return self.callBack.pv_value
|
||||
else:
|
||||
return CaChannel.CaChannel.getValue(self)
|
||||
|
||||
def putWait(self, value, req_type=None, count=None, poll=.01):
|
||||
"""
|
||||
Calls CaChannel.array_put_callback() and waits for the callback to
|
||||
occur before it returns. This allows programs to use array_put_callback()
|
||||
without having to handle asynchronous callbacks.
|
||||
|
||||
Keywords:
|
||||
req_type:
|
||||
See CaChannel.array_put_callback()
|
||||
|
||||
count:
|
||||
See CaChannel.array_put_callback()
|
||||
|
||||
poll:
|
||||
The timeout for pend_event() calls, waiting for the callback
|
||||
to occur. Shorter times reduce the latency at the price of CPU
|
||||
cycles.
|
||||
"""
|
||||
self.callBack.putComplete=0
|
||||
self.array_put_callback(value, req_type, count, putCallBack, self.callBack)
|
||||
while(self.callBack.putComplete == 0):
|
||||
self.pend_event(poll)
|
||||
|
||||
class callBack:
|
||||
"""
|
||||
This class is used by the epicsPV class to handle callbacks. It is required
|
||||
to avoid circular references to the epicsPV object itself when dealing with
|
||||
callbacks, in order to allow the CaChannel destructor to be called.
|
||||
Users will only be interested in the fields that are copied to this class in
|
||||
the callback resulting from a call to epicsPV.getControl().
|
||||
"""
|
||||
def __init__(self):
|
||||
self.newMonitor = 0
|
||||
self.putComplete = 0
|
||||
self.monitorState = 0
|
||||
# monitorState:
|
||||
# 0=not monitored
|
||||
# 1=monitor requested, but no callback yet
|
||||
# 2=monitor requested, callback has arrived
|
||||
|
||||
|
||||
def putCallBack(epicsArgs, userArgs):
|
||||
"""
|
||||
This is the generic callback function used by the epicsPV.putWait() method.
|
||||
It simply sets the callBack.putComplete flag to 1.
|
||||
"""
|
||||
userArgs[0].putComplete=1
|
||||
|
||||
def getCallback(epicsArgs, userArgs):
|
||||
"""
|
||||
This is the generic callback function enabled by the epicsPV.setMonitor() method.
|
||||
It sets the callBack.monitorState flag to 2, indicating that a monitor has
|
||||
been received. It copies all of the attributes in the epicsArgs dictionary
|
||||
to the callBack attribute of the epicsPV object.
|
||||
"""
|
||||
for key in epicsArgs.keys():
|
||||
setattr(userArgs[0], key, epicsArgs[key])
|
||||
if (userArgs[0].monitorState == 1): userArgs[0].monitorState = 2
|
||||
userArgs[0].newMonitor = 1
|
||||
|
||||
Reference in New Issue
Block a user